diff --git a/front/assets/icon/arrow.svg b/front/assets/icon/arrow.svg new file mode 100644 index 0000000..87d213c --- /dev/null +++ b/front/assets/icon/arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/components/actions/ArrowAction.tsx b/front/components/actions/ArrowAction.tsx index 0c8058b..f1a2cc9 100644 --- a/front/components/actions/ArrowAction.tsx +++ b/front/components/actions/ArrowAction.tsx @@ -1,6 +1,6 @@ import "../../style/actions/arrow_action.css" import Draggable from "react-draggable" - +import arrowPng from "../../assets/icon/arrow.svg" import { useRef } from "react" export interface ArrowActionProps { @@ -18,7 +18,7 @@ export default function ArrowAction({ return (
-
+ add arrow ) } + +export function ScreenHead() { + return ( +
+ ) +} + +export function MoveToHead() { + return ( + + + + ) +} + +export function ShootHead() { + return ( + + + + ) +} diff --git a/front/components/actions/RemoveAction.tsx b/front/components/actions/RemoveAction.tsx deleted file mode 100644 index bea72da..0000000 --- a/front/components/actions/RemoveAction.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import RemoveIcon from "../../assets/icon/remove.svg?react" -import "../../style/actions/remove_action.css" - -export interface RemoveActionProps { - onRemove: () => void -} - -export default function RemoveAction({ onRemove }: RemoveActionProps) { - return -} diff --git a/front/components/arrows/BendableArrow.tsx b/front/components/arrows/BendableArrow.tsx index 82c48c1..7855a10 100644 --- a/front/components/arrows/BendableArrow.tsx +++ b/front/components/arrows/BendableArrow.tsx @@ -1,4 +1,12 @@ -import {CSSProperties, ReactElement, RefObject, useCallback, useEffect, useRef, useState,} from "react" +import { + CSSProperties, + ReactElement, + RefObject, + useCallback, + useEffect, + useRef, + useState, +} from "react" import { add, angle, @@ -10,7 +18,6 @@ import { Pos, posWithinBase, ratioWithinBase, - relativeTo, } from "./Pos" import "../../style/bendable_arrows.css" @@ -21,6 +28,7 @@ export interface BendableArrowProps { startPos: Pos segments: Segment[] onSegmentsChanges: (edges: Segment[]) => void + forceStraight: boolean startRadius?: number endRadius?: number @@ -32,6 +40,7 @@ export interface BendableArrowProps { export interface ArrowStyle { width?: number + dashArray?: string head?: () => ReactElement tail?: () => ReactElement } @@ -54,27 +63,19 @@ function constraintInCircle(pos: Pos, from: Pos, radius: number): Pos { } } -function Triangle({fill}: { fill: string }) { - return ( - - - - ) -} - export default function BendableArrow({ - area, - startPos, - - segments, - onSegmentsChanges, - - style, - startRadius = 0, - endRadius = 0, - onDeleteRequested = () => { - }, - }: BendableArrowProps) { + area, + startPos, + + segments, + onSegmentsChanges, + forceStraight, + + style, + startRadius = 0, + endRadius = 0, + onDeleteRequested, +}: BendableArrowProps) { const containerRef = useRef(null) const svgRef = useRef(null) const pathRef = useRef(null) @@ -93,7 +94,7 @@ export default function BendableArrow({ const tailRef = useRef(null) function computeControlPoints(parentBase: DOMRect) { - return segments.flatMap(({next, controlPoint}, i) => { + return segments.flatMap(({ next, controlPoint }, i) => { const prev = i == 0 ? startPos : segments[i - 1].next const prevRelative = posWithinBase(prev, parentBase) @@ -134,34 +135,34 @@ export default function BendableArrow({ }} />, //next pos point (only if this is not the last segment) - i != segments.length - 1 && { - const currentSegment = segments[i] - const newSegments = segments.toSpliced(i, 1, { - ...currentSegment, - next, - }) - onSegmentsChanges(newSegments) - }} - onRemove={() => { - onSegmentsChanges(segments.toSpliced( - Math.max(i - 1, 0), - 1, - ) - ) - }} - onMoves={next => { - setInternalSegments((is) => { - return is.toSpliced(i, 1, { - ...is[i], + i != segments.length - 1 && ( + { + const currentSegment = segments[i] + const newSegments = segments.toSpliced(i, 1, { + ...currentSegment, next, }) - }) - }} - /> + onSegmentsChanges(newSegments) + }} + onRemove={() => { + onSegmentsChanges( + segments.toSpliced(Math.max(i - 1, 0), 1), + ) + }} + onMoves={(next) => { + setInternalSegments((is) => { + return is.toSpliced(i, 1, { + ...is[i], + next, + }) + }) + }} + /> + ), ] }) } @@ -170,21 +171,28 @@ export default function BendableArrow({ const parentBase = area.current!.getBoundingClientRect() const firstSegment = internalSegments[0] ?? null - if (firstSegment == null) throw new Error("segments might not be empty.") + if (firstSegment == null) + throw new Error("segments might not be empty.") const lastSegment = internalSegments[internalSegments.length - 1] const startRelative = posWithinBase(startPos, parentBase) const endRelative = posWithinBase(lastSegment.next, parentBase) - const startNext = firstSegment.controlPoint - ? posWithinBase(firstSegment.controlPoint, parentBase) - : posWithinBase(firstSegment.next, parentBase) - - const endPrevious = lastSegment.controlPoint - ? posWithinBase(lastSegment.controlPoint, parentBase) - : internalSegments[internalSegments.length - 2] - ? posWithinBase(internalSegments[internalSegments.length - 2].next, parentBase) + const startNext = + firstSegment.controlPoint && !forceStraight + ? posWithinBase(firstSegment.controlPoint, parentBase) + : posWithinBase(firstSegment.next, parentBase) + + const endPrevious = forceStraight + ? startRelative + : lastSegment.controlPoint + ? posWithinBase(lastSegment.controlPoint, parentBase) + : internalSegments[internalSegments.length - 2] + ? posWithinBase( + internalSegments[internalSegments.length - 2].next, + parentBase, + ) : startRelative const tailPos = constraintInCircle( @@ -192,11 +200,7 @@ export default function BendableArrow({ startNext, startRadius!, ) - const headPos = constraintInCircle( - endRelative, - endPrevious, - endRadius!, - ) + const headPos = constraintInCircle(endRelative, endPrevious, endRadius!) const left = Math.min(tailPos.x, headPos.x) const top = Math.min(tailPos.y, headPos.y) @@ -224,25 +228,29 @@ export default function BendableArrow({ top: top + "px", } - const segmentsRelatives = internalSegments.map( - ({next, controlPoint}, idx) => { - const nextPos = posWithinBase(next, parentBase) - return { - next: nextPos, - cp: controlPoint + const segmentsRelatives = ( + forceStraight ? internalSegments.slice(-1) : internalSegments + ).map(({ next, controlPoint }, idx) => { + const nextPos = posWithinBase(next, parentBase) + return { + next: nextPos, + cp: + controlPoint && !forceStraight ? posWithinBase(controlPoint, parentBase) : between( - idx == 0 - ? startRelative - : posWithinBase(internalSegments[idx - 1].next, parentBase), - nextPos - ), - } - }, - ) + idx == 0 + ? startRelative + : posWithinBase( + internalSegments[idx - 1].next, + parentBase, + ), + nextPos, + ), + } + }) const computedSegments = segmentsRelatives - .map(({next: n, cp}, idx) => { + .map(({ next: n, cp }, idx) => { let next = n if (idx == internalSegments.length - 1) { @@ -259,7 +267,7 @@ export default function BendableArrow({ const d = `M${tailPos.x - left} ${tailPos.y - top} ` + computedSegments pathRef.current!.setAttribute("d", d) Object.assign(svgRef.current!.style, svgStyle) - }, [startPos, internalSegments]) + }, [startPos, internalSegments, forceStraight]) useEffect(update, [update]) @@ -281,12 +289,16 @@ export default function BendableArrow({ }, [update, containerRef]) useEffect(() => { + if (forceStraight) return const addSegment = (e: MouseEvent) => { const parentBase = area.current!.getBoundingClientRect() - const clickAbsolutePos: Pos = {x: e.x, y: e.y} - const clickPosBaseRatio = ratioWithinBase(clickAbsolutePos, parentBase) + const clickAbsolutePos: Pos = { x: e.x, y: e.y } + const clickPosBaseRatio = ratioWithinBase( + clickAbsolutePos, + parentBase, + ) let segmentInsertionIndex = -1 let segmentInsertionIsOnRightOfCP = false @@ -295,53 +307,60 @@ export default function BendableArrow({ let currentPos = i == 0 ? startPos : segments[i - 1].next let nextPos = segment.next - let controlPointPos = segment.controlPoint ? segment.controlPoint : between(currentPos, nextPos) - - const result = searchOnSegment(currentPos, controlPointPos, nextPos, clickPosBaseRatio, 0.05) - if (result == PointSegmentSearchResult.NOT_FOUND) - continue + let controlPointPos = segment.controlPoint + ? segment.controlPoint + : between(currentPos, nextPos) + + const result = searchOnSegment( + currentPos, + controlPointPos, + nextPos, + clickPosBaseRatio, + 0.05, + ) + if (result == PointSegmentSearchResult.NOT_FOUND) continue segmentInsertionIndex = i - segmentInsertionIsOnRightOfCP = result == PointSegmentSearchResult.RIGHT_TO_CONTROL_POINT + segmentInsertionIsOnRightOfCP = + result == PointSegmentSearchResult.RIGHT_TO_CONTROL_POINT break } - if (segmentInsertionIndex == -1) - return + if (segmentInsertionIndex == -1) return const splicedSegment: Segment = segments[segmentInsertionIndex] - let newSegments: Segment[] - if (segmentInsertionIsOnRightOfCP) { - newSegments = segments.toSpliced( - segmentInsertionIndex, - 1, - {next: clickPosBaseRatio, controlPoint: splicedSegment.controlPoint}, - {next: splicedSegment.next, controlPoint: undefined} - ) - } else { - newSegments = segments.toSpliced( + onSegmentsChanges( + segments.toSpliced( segmentInsertionIndex, 1, - {next: clickPosBaseRatio, controlPoint: undefined}, - {next: splicedSegment.next, controlPoint: splicedSegment.controlPoint} - ) - } - - onSegmentsChanges(newSegments) + { + next: clickPosBaseRatio, + controlPoint: segmentInsertionIsOnRightOfCP + ? splicedSegment.controlPoint + : undefined, + }, + { + next: splicedSegment.next, + controlPoint: segmentInsertionIsOnRightOfCP + ? undefined + : splicedSegment.controlPoint, + }, + ), + ) } - pathRef?.current?.addEventListener('dblclick', addSegment) + pathRef?.current?.addEventListener("dblclick", addSegment) return () => { - pathRef?.current?.removeEventListener('dblclick', addSegment) + pathRef?.current?.removeEventListener("dblclick", addSegment) } - }, [pathRef, segments]); + }, [pathRef, segments, onSegmentsChanges]) return (
+ style={{ position: "absolute", top: 0, left: 0 }}> { - if (e.key == "Delete") onDeleteRequested() + if (onDeleteRequested && e.key == "Delete") + onDeleteRequested() }} />
- {style?.head?.call(style) ?? } + {style?.head?.call(style)}
- {style?.tail?.call(style) ?? } + {style?.tail?.call(style)}
- {isSelected && computeControlPoints(area.current!.getBoundingClientRect())} + {!forceStraight && + isSelected && + computeControlPoints(area.current!.getBoundingClientRect())}
) } @@ -394,13 +417,20 @@ interface ControlPointProps { enum PointSegmentSearchResult { LEFT_TO_CONTROL_POINT, RIGHT_TO_CONTROL_POINT, - NOT_FOUND + NOT_FOUND, } -function searchOnSegment(startPos: Pos, controlPoint: Pos, endPos: Pos, point: Pos, minDistance: number): PointSegmentSearchResult { - - - const step = 1 / ((distance(startPos, controlPoint) + distance(controlPoint, endPos)) / minDistance) +function searchOnSegment( + startPos: Pos, + controlPoint: Pos, + endPos: Pos, + point: Pos, + minDistance: number, +): PointSegmentSearchResult { + const step = + 1 / + ((distance(startPos, controlPoint) + distance(controlPoint, endPos)) / + minDistance) const p0MinusP1 = minus(startPos, controlPoint) const p2MinusP1 = minus(endPos, controlPoint) @@ -408,22 +438,12 @@ function searchOnSegment(startPos: Pos, controlPoint: Pos, endPos: Pos, point: P function getDistanceAt(t: number): number { // apply the bezier function const pos = add( - add( - controlPoint, - mul( - p0MinusP1, - (1 - t) ** 2 - ) - ), - mul( - p2MinusP1, - t ** 2 - ) + add(controlPoint, mul(p0MinusP1, (1 - t) ** 2)), + mul(p2MinusP1, t ** 2), ) return distance(pos, point) } - for (let t = 0; t < 1; t += step) { if (getDistanceAt(t) <= minDistance) return t >= 0.5 @@ -439,18 +459,18 @@ let slice = 0.5 for (let i = 0; i < 100; i++) { t += slice slice /= 2 -// console.log(t) + // console.log(t) } function ArrowPoint({ - className, - posRatio, - parentBase, - onMoves, - onPosValidated, - onRemove, - radius = 7, - }: ControlPointProps) { + className, + posRatio, + parentBase, + onMoves, + onPosValidated, + onRemove, + radius = 7, +}: ControlPointProps) { const ref = useRef(null) const pos = posWithinBase(posRatio, parentBase) @@ -466,7 +486,7 @@ function ArrowPoint({ const pointPos = middlePos(ref.current!.getBoundingClientRect()) onMoves(ratioWithinBase(pointPos, parentBase)) }} - position={{x: pos.x - radius, y: pos.y - radius}}> + position={{ x: pos.x - radius, y: pos.y - radius }}>
p.x) - const allPosY = positions.map(p => p.y) + const allPosX = positions.map((p) => p.x) + const allPosY = positions.map((p) => p.y) const x = Math.min(...allPosX) const y = Math.min(...allPosY) const width = Math.max(...allPosX) - x const height = Math.max(...allPosY) - y - return {x, y, width, height} + return { x, y, width, height } } export function surrounds(pos: Pos, width: number, height: number): Box { return { - x: pos.x + (width / 2), - y: pos.y + (height / 2), + x: pos.x + width / 2, + y: pos.y + height / 2, width, - height + height, } } export function contains(box: Box, pos: Pos): boolean { - return (pos.x >= box.x && pos.x <= box.x + box.width && pos.y >= box.y && pos.y <= box.y + box.height) -} \ No newline at end of file + return ( + pos.x >= box.x && + pos.x <= box.x + box.width && + pos.y >= box.y && + pos.y <= box.y + box.height + ) +} diff --git a/front/components/arrows/Pos.ts b/front/components/arrows/Pos.ts index 08ddc7a..4ca08bd 100644 --- a/front/components/arrows/Pos.ts +++ b/front/components/arrows/Pos.ts @@ -3,7 +3,7 @@ export interface Pos { y: number } -export const NULL_POS: Pos = {x: 0, y: 0} +export const NULL_POS: Pos = { x: 0, y: 0 } /** * Returns position of a relative to b @@ -11,7 +11,7 @@ export const NULL_POS: Pos = {x: 0, y: 0} * @param b */ export function relativeTo(a: Pos, b: Pos): Pos { - return {x: a.x - b.x, y: a.y - b.y} + return { x: a.x - b.x, y: a.y - b.y } } /** @@ -19,20 +19,19 @@ export function relativeTo(a: Pos, b: Pos): Pos { * @param rect */ export function middlePos(rect: DOMRect): Pos { - return {x: rect.x + rect.width / 2, y: rect.y + rect.height / 2} + return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 } } - export function add(a: Pos, b: Pos): Pos { - return {x: a.x + b.x, y: a.y + b.y} + return { x: a.x + b.x, y: a.y + b.y } } export function minus(a: Pos, b: Pos): Pos { - return {x: a.x - b.x, y: a.y - b.y} + return { x: a.x - b.x, y: a.y - b.y } } export function mul(a: Pos, t: number): Pos { - return {x: a.x * t, y: a.y * t} + return { x: a.x * t, y: a.y * t } } export function distance(a: Pos, b: Pos): number { @@ -61,6 +60,6 @@ export function posWithinBase(ratio: Pos, base: DOMRect): Pos { export function between(a: Pos, b: Pos): Pos { return { x: a.x / 2 + b.x / 2, - y: a.y / 2 + b.y / 2 + y: a.y / 2 + b.y / 2, } -} \ No newline at end of file +} diff --git a/front/components/editor/BasketCourt.tsx b/front/components/editor/BasketCourt.tsx index be8e22d..cfa4bd2 100644 --- a/front/components/editor/BasketCourt.tsx +++ b/front/components/editor/BasketCourt.tsx @@ -9,14 +9,13 @@ import { } from "react" import CourtPlayer from "./CourtPlayer" import { Player } from "../../tactic/Player" -import { Action, MovementActionKind } from "../../tactic/Action" -import RemoveAction from "../actions/RemoveAction" +import { Action, ActionKind } from "../../tactic/Action" import ArrowAction from "../actions/ArrowAction" - -import BendableArrow, { Segment } from "../arrows/BendableArrow" -import { middlePos, NULL_POS, Pos, ratioWithinBase } from "../arrows/Pos" +import { middlePos, ratioWithinBase } from "../arrows/Pos" import BallAction from "../actions/BallAction" import { CourtObject } from "../../tactic/CourtObjects" +import { contains } from "../arrows/Box" +import { CourtAction } from "../../views/editor/CourtAction" export interface BasketCourtProps { players: Player[] @@ -32,7 +31,7 @@ export interface BasketCourtProps { onBallRemove: () => void onBallMoved: (ball: DOMRect) => void - courtImage: () => ReactElement + courtImage: ReactElement courtRef: RefObject } @@ -51,9 +50,16 @@ export function BasketCourt({ courtImage, courtRef, }: BasketCourtProps) { - function placeArrow(originRef: HTMLElement, arrowHead: DOMRect) { + function placeArrow(origin: Player, arrowHead: DOMRect) { + const originRef = document.getElementById(origin.id)! + const courtBounds = courtRef.current!.getBoundingClientRect() + const start = ratioWithinBase( + middlePos(originRef.getBoundingClientRect()), + courtBounds, + ) + for (const player of players) { - if (player.id == originRef.id) { + if (player.id == origin.id) { continue } @@ -73,18 +79,12 @@ export function BasketCourt({ .getElementById(player.id)! .getBoundingClientRect() - const courtBounds = courtRef.current!.getBoundingClientRect() - - const start = ratioWithinBase( - middlePos(originRef.getBoundingClientRect()), - courtBounds, - ) const end = ratioWithinBase(middlePos(targetPos), courtBounds) const action: Action = { fromPlayerId: originRef.id, toPlayerId: player.id, - type: MovementActionKind.SCREEN, + type: origin.hasBall ? ActionKind.SHOOT : ActionKind.SCREEN, moveFrom: start, segments: [{ next: end }], } @@ -95,18 +95,19 @@ export function BasketCourt({ const action: Action = { fromPlayerId: originRef.id, - type: MovementActionKind.MOVE, - moveFrom: middlePos(originRef.getBoundingClientRect()), - segments: [{ next: middlePos(arrowHead) }], + type: origin.hasBall ? ActionKind.DRIBBLE : ActionKind.MOVE, + moveFrom: ratioWithinBase( + middlePos(originRef.getBoundingClientRect()), + courtBounds, + ), + segments: [ + { next: ratioWithinBase(middlePos(arrowHead), courtBounds) }, + ], } setActions((actions) => [...actions, action]) } - const [previewArrowOriginPos, setPreviewArrowOriginPos] = - useState(NULL_POS) - const [isPreviewArrowEnabled, setPreviewArrowEnabled] = useState(false) - - const [previewArrowEdges, setPreviewArrowEdges] = useState([]) + const [previewAction, setPreviewAction] = useState(null) const updateActionsRelatedTo = useCallback((player: Player) => { const newPos = ratioWithinBase( @@ -147,9 +148,7 @@ export function BasketCourt({ className="court-container" ref={courtRef} style={{ position: "relative" }}> - {courtImage()} - - {internActions.map((action, idx) => renderAction(action, idx))} + {courtImage} {players.map((player) => ( onPlayerRemove(player)} parentRef={courtRef} availableActions={(pieceRef) => [ - onPlayerRemove(player)} - />, { const baseBounds = courtRef.current!.getBoundingClientRect() - setPreviewArrowEdges([ - { - next: ratioWithinBase( - middlePos(headPos), - baseBounds, + + const arrowHeadPos = middlePos(headPos) + + const target = players.find( + (p) => + p != player && + contains( + document + .getElementById(p.id)! + .getBoundingClientRect(), + arrowHeadPos, ), - }, - ]) + ) + + setPreviewAction((action) => ({ + ...action!, + segments: [ + { + next: ratioWithinBase( + arrowHeadPos, + baseBounds, + ), + }, + ], + type: player.hasBall + ? target + ? ActionKind.SHOOT + : ActionKind.DRIBBLE + : target + ? ActionKind.SCREEN + : ActionKind.MOVE, + })) }} onHeadPicked={(headPos) => { + ;(document.activeElement as HTMLElement).blur() const baseBounds = courtRef.current!.getBoundingClientRect() - setPreviewArrowOriginPos( - ratioWithinBase( + setPreviewAction({ + type: player.hasBall + ? ActionKind.DRIBBLE + : ActionKind.MOVE, + fromPlayerId: player.id, + toPlayerId: undefined, + moveFrom: ratioWithinBase( middlePos( pieceRef.getBoundingClientRect(), ), baseBounds, ), - ) - setPreviewArrowEdges([ - { - next: ratioWithinBase( - middlePos(headPos), - baseBounds, - ), - }, - ]) - setPreviewArrowEnabled(true) + segments: [ + { + next: ratioWithinBase( + middlePos(headPos), + baseBounds, + ), + }, + ], + }) }} onHeadDropped={(headRect) => { - placeArrow(pieceRef, headRect) - setPreviewArrowEnabled(false) + placeArrow(player, headRect) + setPreviewAction(null) }} />, player.hasBall && ( onBallMoved(ref.getBoundingClientRect()) } @@ -217,6 +241,8 @@ export function BasketCourt({ /> ))} + {internActions.map((action, idx) => renderAction(action, idx))} + {objects.map((object) => { if (object.type == "ball") { return ( @@ -231,16 +257,13 @@ export function BasketCourt({ throw new Error("unknown court object", object.type) })} - {isPreviewArrowEnabled && ( - {}} - //TODO place those values in constants - endRadius={17} - startRadius={26} + onActionDeleted={() => {}} + onActionChanges={() => {}} /> )}
diff --git a/front/style/actions/arrow_action.css b/front/style/actions/arrow_action.css index 827503e..3aa88d7 100644 --- a/front/style/actions/arrow_action.css +++ b/front/style/actions/arrow_action.css @@ -2,31 +2,22 @@ height: 50%; } -.arrow-action-pin, -.arrow-head-pick { - position: absolute; - min-width: 10px; - min-height: 10px; - border-radius: 100px; - background-color: red; - cursor: grab; +.arrow-action-icon { + user-select: none; + -moz-user-select: none; + max-width: 17px; + max-height: 17px; } .arrow-head-pick { - background-color: red; -} - -.arrow-head-xarrow { - visibility: visible; + position: absolute; + cursor: grab; + top: 0; + left: 0; + min-width: 17px; + min-height: 17px; } -.arrow-action:active .arrow-head-xarrow { - visibility: visible; +.arrow-head-pick:active { + cursor: crosshair; } - -/*.arrow-action:active .arrow-head-pick {*/ -/* min-height: unset;*/ -/* min-width: unset;*/ -/* width: 0;*/ -/* height: 0;*/ -/*}*/ diff --git a/front/style/player.css b/front/style/player.css index 326be7d..22afe4e 100644 --- a/front/style/player.css +++ b/front/style/player.css @@ -41,15 +41,15 @@ position: absolute; flex-direction: row; - justify-content: space-between; + justify-content: space-evenly; align-content: space-between; align-items: center; visibility: hidden; - margin-bottom: 10%; - transform: translateY(-20px); + transform: translateY(-25px); height: 20px; + width: 150%; gap: 25%; } diff --git a/front/tactic/Action.ts b/front/tactic/Action.ts index 993499a..d66f375 100644 --- a/front/tactic/Action.ts +++ b/front/tactic/Action.ts @@ -2,13 +2,14 @@ import { Pos } from "../components/arrows/Pos" import { Segment } from "../components/arrows/BendableArrow" import { PlayerId } from "./Player" -export enum MovementActionKind { +export enum ActionKind { SCREEN = "SCREEN", DRIBBLE = "DRIBBLE", MOVE = "MOVE", + SHOOT = "SHOOT", } -export type Action = { type: MovementActionKind } & MovementAction +export type Action = { type: ActionKind } & MovementAction export interface MovementAction { fromPlayerId: PlayerId diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 7bd1a4c..930c99a 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -31,6 +31,7 @@ import { CourtObject } from "../tactic/CourtObjects" import { CourtAction } from "./editor/CourtAction" import { BasketCourt } from "../components/editor/BasketCourt" import { ratioWithinBase } from "../components/arrows/Pos" +import { Action, ActionKind } from "../tactic/Action" const ERROR_STYLE: CSSProperties = { borderColor: "red", @@ -254,16 +255,42 @@ function EditorView({ return -1 } + function updateActions(actions: Action[], players: Player[]) { + return actions.map((action) => { + const originHasBall = players.find( + (p) => p.id == action.fromPlayerId, + )!.hasBall + + let type = action.type + + if (originHasBall && type == ActionKind.MOVE) { + type = ActionKind.DRIBBLE + } else if (originHasBall && type == ActionKind.SCREEN) { + type = ActionKind.SHOOT + } else if (type == ActionKind.DRIBBLE) { + type = ActionKind.MOVE + } else if (type == ActionKind.SHOOT) { + type = ActionKind.SCREEN + } + return { + ...action, + type, + } + }) + } + const onBallDropOnPlayer = (playerCollidedIdx: number) => { setContent((content) => { const ballObj = content.objects.findIndex((o) => o.type == "ball") let player = content.players.at(playerCollidedIdx) as Player + const players = content.players.toSpliced(playerCollidedIdx, 1, { + ...player, + hasBall: true, + }) return { ...content, - players: content.players.toSpliced(playerCollidedIdx, 1, { - ...player, - hasBall: true, - }), + actions: updateActions(content.actions, players), + players, objects: content.objects.toSpliced(ballObj, 1), } }) @@ -303,13 +330,16 @@ function EditorView({ bottomRatio: y, } + const players = content.players.map((player) => ({ + ...player, + hasBall: false, + })) + setContent((content) => { return { ...content, - players: content.players.map((player) => ({ - ...player, - hasBall: false, - })), + actions: updateActions(content.actions, players), + players, objects: [...content.objects, courtObject], } }) @@ -436,7 +466,7 @@ function EditorView({ objects={content.objects} actions={content.actions} onBallMoved={onBallDrop} - courtImage={() => } + courtImage={} courtRef={courtDivContentRef} setActions={(actions) => setContent((content) => ({ diff --git a/front/views/editor/CourtAction.tsx b/front/views/editor/CourtAction.tsx index 7a2e037..44118e3 100644 --- a/front/views/editor/CourtAction.tsx +++ b/front/views/editor/CourtAction.tsx @@ -1,6 +1,11 @@ -import { Action } from "../../tactic/Action" +import { Action, ActionKind } from "../../tactic/Action" import BendableArrow from "../../components/arrows/BendableArrow" import { RefObject } from "react" +import { + MoveToHead, + ScreenHead, + ShootHead, +} from "../../components/actions/ArrowAction" export interface CourtActionProps { action: Action @@ -15,8 +20,31 @@ export function CourtAction({ onActionDeleted, courtRef, }: CourtActionProps) { + let head + switch (action.type) { + case ActionKind.DRIBBLE: + case ActionKind.MOVE: + head = () => + break + case ActionKind.SCREEN: + head = () => + break + case ActionKind.SHOOT: + head = () => + } + + let dashArray + switch (action.type) { + case ActionKind.SHOOT: + dashArray = "10 5" + break + case ActionKind.DRIBBLE: + dashArray = "4" + } + return ( ) }