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={() =>