use one array of TacticComponent

pull/95/head
maxime.batista 1 year ago committed by maxime
parent a3e38bded1
commit 8d444c38b4

@ -22,7 +22,7 @@ import {
ratioWithinBase,
relativeTo,
norm,
} from "./Pos"
} from "../../geo/Pos"
import "../../style/bendable_arrows.css"
import Draggable from "react-draggable"

@ -1,7 +1,8 @@
import "../../style/ball.css"
import BallSvg from "../../assets/icon/ball.svg?react"
import {BALL_ID} from "../../model/tactic/Ball";
export function BallPiece() {
return <BallSvg className={"ball"} />
return <BallSvg id={BALL_ID} className={"ball"}/>
}

@ -12,16 +12,17 @@ import CourtPlayer from "./CourtPlayer"
import { Player } from "../../model/tactic/Player"
import { Action, ActionKind } from "../../model/tactic/Action"
import ArrowAction from "../actions/ArrowAction"
import { middlePos, ratioWithinBase } from "../arrows/Pos"
import { middlePos, ratioWithinBase } from "../../geo/Pos"
import BallAction from "../actions/BallAction"
import { CourtObject } from "../../model/tactic/Ball"
import { contains } from "../arrows/Box"
import {BALL_ID} from "../../model/tactic/Ball"
import { contains, overlaps } from "../../geo/Box"
import { CourtAction } from "../../views/editor/CourtAction"
import { TacticComponent } from "../../model/tactic/Tactic"
export interface BasketCourtProps {
players: Player[]
components: TacticComponent[]
actions: Action[]
objects: CourtObject[]
renderAction: (a: Action, key: number) => ReactElement
setActions: (f: (a: Action[]) => Action[]) => void
@ -37,9 +38,8 @@ export interface BasketCourtProps {
}
export function BasketCourt({
players,
components,
actions,
objects,
renderAction,
setActions,
onPlayerRemove,
@ -59,33 +59,31 @@ export function BasketCourt({
courtBounds,
)
for (const player of players) {
if (player.id == origin.id) {
for (const component of components) {
if (component.id == origin.id) {
continue
}
const playerBounds = document
.getElementById(player.id)!
.getElementById(component.id)!
.getBoundingClientRect()
if (
!(
playerBounds.top > arrowHead.bottom ||
playerBounds.right < arrowHead.left ||
playerBounds.bottom < arrowHead.top ||
playerBounds.left > arrowHead.right
)
) {
if (overlaps(playerBounds, arrowHead)) {
const targetPos = document
.getElementById(player.id)!
.getElementById(component.id)!
.getBoundingClientRect()
const end = ratioWithinBase(middlePos(targetPos), courtBounds)
const action: Action = {
fromPlayerId: originRef.id,
toPlayerId: player.id,
type: origin.hasBall ? ActionKind.SHOOT : ActionKind.SCREEN,
fromId: originRef.id,
toId: component.id,
type:
component.type == "player"
? origin.hasBall
? ActionKind.SHOOT
: ActionKind.SCREEN
: ActionKind.MOVE,
moveFrom: start,
segments: [{ next: end }],
}
@ -95,7 +93,7 @@ export function BasketCourt({
}
const action: Action = {
fromPlayerId: originRef.id,
fromId: originRef.id,
type: origin.hasBall ? ActionKind.DRIBBLE : ActionKind.MOVE,
moveFrom: ratioWithinBase(
middlePos(originRef.getBoundingClientRect()),
@ -110,20 +108,20 @@ export function BasketCourt({
const [previewAction, setPreviewAction] = useState<Action | null>(null)
const updateActionsRelatedTo = useCallback((player: Player) => {
const updateActionsRelatedTo = useCallback((comp: TacticComponent) => {
const newPos = ratioWithinBase(
middlePos(
document.getElementById(player.id)!.getBoundingClientRect(),
document.getElementById(comp.id)!.getBoundingClientRect(),
),
courtRef.current!.getBoundingClientRect(),
)
setActions((actions) =>
actions.map((a) => {
if (a.fromPlayerId == player.id) {
if (a.fromId == comp.id) {
return { ...a, moveFrom: newPos }
}
if (a.toPlayerId == player.id) {
if (a.toId == comp.id) {
const segments = a.segments.toSpliced(
a.segments.length - 1,
1,
@ -151,113 +149,125 @@ export function BasketCourt({
style={{ position: "relative" }}>
{courtImage}
{players.map((player) => (
<CourtPlayer
key={player.id}
player={player}
onDrag={() => updateActionsRelatedTo(player)}
onChange={onPlayerChange}
onRemove={() => onPlayerRemove(player)}
courtRef={courtRef}
availableActions={(pieceRef) => [
<ArrowAction
key={1}
onHeadMoved={(headPos) => {
const baseBounds =
courtRef.current!.getBoundingClientRect()
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()
setPreviewAction({
type: player.hasBall
? ActionKind.DRIBBLE
: ActionKind.MOVE,
fromPlayerId: player.id,
toPlayerId: undefined,
moveFrom: ratioWithinBase(
middlePos(
pieceRef.getBoundingClientRect(),
),
baseBounds,
),
segments: [
{
next: ratioWithinBase(
middlePos(headPos),
baseBounds,
),
},
],
})
}}
onHeadDropped={(headRect) => {
placeArrow(player, headRect)
setPreviewAction(null)
}}
/>,
player.hasBall && (
<BallAction
key={2}
onDrop={(ref) =>
onBallMoved(ref.getBoundingClientRect())
}
/>
),
]}
/>
))}
{components.map((component) => {
if (component.type == "player") {
const player = component
return (
<CourtPlayer
key={player.id}
player={player}
onDrag={() => updateActionsRelatedTo(player)}
onChange={onPlayerChange}
onRemove={() => onPlayerRemove(player)}
courtRef={courtRef}
availableActions={(pieceRef) => [
<ArrowAction
key={1}
onHeadMoved={(headPos) => {
const baseBounds =
courtRef.current!.getBoundingClientRect()
{internActions.map((action, idx) => renderAction(action, idx))}
const arrowHeadPos = middlePos(headPos)
const target = components.find(
(c) =>
c.id != player.id &&
contains(
document
.getElementById(c.id)!
.getBoundingClientRect(),
arrowHeadPos,
),
)
{objects.map((object) => {
if (object.type == "ball") {
const type =
target?.type == "player"
? player.hasBall
? target
? ActionKind.SHOOT
: ActionKind.DRIBBLE
: target
? ActionKind.SCREEN
: ActionKind.MOVE
: ActionKind.MOVE
setPreviewAction((action) => ({
...action!,
segments: [
{
next: ratioWithinBase(
arrowHeadPos,
baseBounds,
),
},
],
type,
}))
}}
onHeadPicked={(headPos) => {
;(
document.activeElement as HTMLElement
).blur()
const baseBounds =
courtRef.current!.getBoundingClientRect()
setPreviewAction({
type: player.hasBall
? ActionKind.DRIBBLE
: ActionKind.MOVE,
fromId: player.id,
toId: undefined,
moveFrom: ratioWithinBase(
middlePos(
pieceRef.getBoundingClientRect(),
),
baseBounds,
),
segments: [
{
next: ratioWithinBase(
middlePos(headPos),
baseBounds,
),
},
],
})
}}
onHeadDropped={(headRect) => {
placeArrow(player, headRect)
setPreviewAction(null)
}}
/>,
player.hasBall && (
<BallAction
key={2}
onDrop={(ref) =>
onBallMoved(
ref.getBoundingClientRect(),
)
}
/>
),
]}
/>
)
}
if (component.type == BALL_ID) {
return (
<CourtBall
onMoved={onBallMoved}
ball={object}
onPosValidated={onBallMoved}
onMoves={() => updateActionsRelatedTo(component)}
ball={component}
onRemove={onBallRemove}
key="ball"
/>
)
}
throw new Error("unknown court object" + object.type)
throw new Error("unknown tactic component " + component)
})}
{internActions.map((action, idx) => renderAction(action, idx))}
{previewAction && (
<CourtAction
courtRef={courtRef}

@ -4,12 +4,18 @@ import { BallPiece } from "./BallPiece"
import { Ball } from "../../model/tactic/Ball"
export interface CourtBallProps {
onMoved: (rect: DOMRect) => void
onPosValidated: (rect: DOMRect) => void
onMoves: () => void
onRemove: () => void
ball: Ball
}
export function CourtBall({ onMoved, ball, onRemove }: CourtBallProps) {
export function CourtBall({
onPosValidated,
ball,
onRemove,
onMoves,
}: CourtBallProps) {
const pieceRef = useRef<HTMLDivElement>(null)
const x = ball.rightRatio
@ -17,7 +23,10 @@ export function CourtBall({ onMoved, ball, onRemove }: CourtBallProps) {
return (
<Draggable
onStop={() => onMoved(pieceRef.current!.getBoundingClientRect())}
onStop={() =>
onPosValidated(pieceRef.current!.getBoundingClientRect())
}
onDrag={onMoves}
nodeRef={pieceRef}>
<div
className={"ball-div"}

@ -3,7 +3,7 @@ import "../../style/player.css"
import Draggable from "react-draggable"
import { PlayerPiece } from "./PlayerPiece"
import { Player } from "../../model/tactic/Player"
import { NULL_POS, ratioWithinBase } from "../arrows/Pos"
import { NULL_POS, ratioWithinBase } from "../../geo/Pos"
export interface PlayerProps {
player: Player
@ -44,13 +44,14 @@ export default function CourtPlayer({
const { x, y } = ratioWithinBase(pieceBounds, parentBounds)
onChange({
type: "player",
id: player.id,
rightRatio: x,
bottomRatio: y,
team: player.team,
role: player.role,
hasBall: player.hasBall,
})
} as Player)
}}>
<div
id={player.id}

@ -28,6 +28,14 @@ export function surrounds(pos: Pos, width: number, height: number): Box {
}
}
export function overlaps(a: Box, b: Box): boolean {
if (a.x + a.width < b.x || b.x + b.width < a.x) {
return false
}
return !(a.y + a.height < b.y || b.y + b.height < a.y)
}
export function contains(box: Box, pos: Pos): boolean {
return (
pos.x >= box.x &&

@ -1,6 +1,7 @@
import { Pos } from "../../components/arrows/Pos"
import { Pos } from "../../geo/Pos"
import { Segment } from "../../components/arrows/BendableArrow"
import { PlayerId } from "./Player"
import { ComponentId } from "./Tactic"
export enum ActionKind {
SCREEN = "SCREEN",
@ -12,8 +13,8 @@ export enum ActionKind {
export type Action = { type: ActionKind } & MovementAction
export interface MovementAction {
fromPlayerId: PlayerId
toPlayerId?: PlayerId
fromId: ComponentId
toId?: ComponentId
moveFrom: Pos
segments: Segment[]
}

@ -1,17 +1,9 @@
export type CourtObject = { type: "ball" } & Ball
import { Component } from "./Tactic"
export interface Ball {
/**
* The ball is a "ball" court object
*/
readonly type: "ball"
export const BALL_ID = "ball"
export const BALL_TYPE = "ball"
/**
* Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle)
*/
readonly bottomRatio: number
/**
* Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle)
*/
readonly rightRatio: number
}
//place here all different kinds of objects
export type CourtObject = Ball
export type Ball = Component<typeof BALL_TYPE>

@ -1,3 +1,5 @@
import {Component} from "./Tactic";
export type PlayerId = string
export enum PlayerTeam {
@ -7,7 +9,9 @@ export enum PlayerTeam {
export interface Player {
readonly id: PlayerId
}
export interface Player extends Component<"player"> {
/**
* the player's team
* */
@ -18,18 +22,9 @@ export interface Player {
* */
readonly role: string
/**
* Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle)
*/
readonly bottomRatio: number
/**
* Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle)
*/
readonly rightRatio: number
/**
* True if the player has a basketball
*/
readonly hasBall: boolean
}

@ -1,6 +1,6 @@
import { Player } from "./Player"
import { CourtObject } from "./Ball"
import { Action } from "./Action"
import {Player} from "./Player"
import {Action} from "./Action"
import {CourtObject} from "./Ball"
export interface Tactic {
id: number
@ -9,7 +9,29 @@ export interface Tactic {
}
export interface TacticContent {
players: Player[]
objects: CourtObject[]
components: TacticComponent[]
actions: Action[]
}
export type TacticComponent = Player | CourtObject
export type ComponentId = string
export interface Component<T> {
/**
* The component's type
*/
readonly type: T
/**
* The component's identifier
*/
readonly id: ComponentId
/**
* Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle)
*/
readonly bottomRatio: number
/**
* Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle)
*/
readonly rightRatio: number
}

@ -1,38 +1,27 @@
import {
CSSProperties,
Dispatch,
SetStateAction,
useCallback,
useMemo,
useRef,
useState,
} from "react"
import {CSSProperties, Dispatch, SetStateAction, useCallback, useMemo, useRef, useState,} from "react"
import "../style/editor.css"
import TitleInput from "../components/TitleInput"
import PlainCourt from "../assets/court/full_court.svg?react"
import HalfCourt from "../assets/court/half_court.svg?react"
import { BallPiece } from "../components/editor/BallPiece"
import {BallPiece} from "../components/editor/BallPiece"
import { Rack } from "../components/Rack"
import { PlayerPiece } from "../components/editor/PlayerPiece"
import { Player } from "../model/tactic/Player"
import {Rack} from "../components/Rack"
import {PlayerPiece} from "../components/editor/PlayerPiece"
import {Player, PlayerTeam} from "../model/tactic/Player"
import { Tactic, TacticContent } from "../model/tactic/Tactic"
import { fetchAPI } from "../Fetcher"
import { PlayerTeam } from "../model/tactic/Player"
import {Tactic, TacticComponent, TacticContent} from "../model/tactic/Tactic"
import {fetchAPI} from "../Fetcher"
import SavingState, {
SaveState,
SaveStates,
} from "../components/editor/SavingState"
import SavingState, {SaveState, SaveStates,} from "../components/editor/SavingState"
import { CourtObject } from "../model/tactic/Ball"
import { CourtAction } from "./editor/CourtAction"
import { BasketCourt } from "../components/editor/BasketCourt"
import { ratioWithinBase } from "../components/arrows/Pos"
import { Action, ActionKind } from "../model/tactic/Action"
import { BASE } from "../Constants"
import {BALL_ID, BALL_TYPE, CourtObject, Ball} from "../model/tactic/Ball"
import {CourtAction} from "./editor/CourtAction"
import {BasketCourt} from "../components/editor/BasketCourt"
import {Action, ActionKind} from "../model/tactic/Action"
import {BASE} from "../Constants"
import {overlaps} from "../geo/Box"
import {ratioWithinBase} from "../geo/Pos"
const ERROR_STYLE: CSSProperties = {
borderColor: "red",
@ -135,10 +124,10 @@ function EditorView({
)
const [allies, setAllies] = useState(
getRackPlayers(PlayerTeam.Allies, content.players),
getRackPlayers(PlayerTeam.Allies, content.components),
)
const [opponents, setOpponents] = useState(
getRackPlayers(PlayerTeam.Opponents, content.players),
getRackPlayers(PlayerTeam.Opponents, content.components),
)
const [objects, setObjects] = useState<RackedCourtObject[]>(
@ -151,15 +140,10 @@ function EditorView({
const courtBounds = courtDivContentRef.current!.getBoundingClientRect()
// check if refBounds overlaps courtBounds
return !(
bounds.top > courtBounds.bottom ||
bounds.right < courtBounds.left ||
bounds.bottom < courtBounds.top ||
bounds.left > courtBounds.right
)
return overlaps(courtBounds, bounds)
}
const onPieceDetach = (ref: HTMLDivElement, element: RackedPlayer) => {
const onRackPieceDetach = (ref: HTMLDivElement, element: RackedPlayer) => {
const refBounds = ref.getBoundingClientRect()
const courtBounds = courtDivContentRef.current!.getBoundingClientRect()
@ -168,23 +152,24 @@ function EditorView({
setContent((content) => {
return {
...content,
players: [
...content.players,
components: [
...content.components,
{
type: "player",
id: "player-" + element.key + "-" + element.team,
team: element.team,
role: element.key,
rightRatio: x,
bottomRatio: y,
hasBall: false,
},
} as Player,
],
actions: content.actions,
}
})
}
const onObjectDetach = (
const onRackedObjectDetach = (
ref: HTMLDivElement,
rackedObject: RackedCourtObject,
) => {
@ -196,27 +181,22 @@ function EditorView({
let courtObject: CourtObject
switch (rackedObject.key) {
case "ball":
const ballObj = content.objects.findIndex(
(o) => o.type == "ball",
case BALL_TYPE:
const ballObj = content.components.findIndex(
(o) => o.type == BALL_TYPE,
)
const playerCollidedIdx = getPlayerCollided(
const playerCollidedIdx = getComponentCollided(
refBounds,
content.players,
content.components.toSpliced(ballObj, 1),
)
if (playerCollidedIdx != -1) {
onBallDropOnPlayer(playerCollidedIdx)
setContent((content) => {
return {
...content,
objects: content.objects.toSpliced(ballObj, 1),
}
})
onBallDropOnComponent(playerCollidedIdx)
return
}
courtObject = {
type: "ball",
type: BALL_TYPE,
id: BALL_ID,
rightRatio: x,
bottomRatio: y,
}
@ -229,38 +209,34 @@ function EditorView({
setContent((content) => {
return {
...content,
objects: [...content.objects, courtObject],
components: [...content.components, courtObject],
}
})
}
const getPlayerCollided = (
const getComponentCollided = (
bounds: DOMRect,
players: Player[],
components: TacticComponent[],
): number | -1 => {
for (let i = 0; i < players.length; i++) {
const player = players[i]
for (let i = 0; i < components.length; i++) {
const component = components[i]
const playerBounds = document
.getElementById(player.id)!
.getElementById(component.id)!
.getBoundingClientRect()
const doesOverlap = !(
bounds.top > playerBounds.bottom ||
bounds.right < playerBounds.left ||
bounds.bottom < playerBounds.top ||
bounds.left > playerBounds.right
)
if (doesOverlap) {
if (overlaps(playerBounds, bounds)) {
return i
}
}
return -1
}
function updateActions(actions: Action[], players: Player[]) {
function updateActions(actions: Action[], components: TacticComponent[]) {
return actions.map((action) => {
const originHasBall = players.find(
(p) => p.id == action.fromPlayerId,
)!.hasBall
const originHasBall = (
components.find(
(p) => p.type == "player" && p.id == action.fromId,
)! as Player
).hasBall
let type = action.type
@ -280,80 +256,101 @@ function EditorView({
})
}
const onBallDropOnPlayer = (playerCollidedIdx: number) => {
const onBallDropOnComponent = (collidedComponentIdx: 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,
})
const ballObj = content.components.findIndex(
(p) => p.type == BALL_TYPE,
)
let component = content.components[collidedComponentIdx]
if (component.type != "player") {
return content //do nothing if the ball isn't dropped on a player.
}
const components = content.components.toSpliced(
collidedComponentIdx,
1,
{
...component,
hasBall: true,
},
)
// Maybe the ball is not present on the court as an object component
// if so, don't bother removing it from the court.
// This can occur if the user drags and drop the ball from a player that already has the ball
// to another component
if (ballObj != -1) {
components.splice(ballObj, 1)
}
return {
...content,
actions: updateActions(content.actions, players),
players,
objects: content.objects.toSpliced(ballObj, 1),
actions: updateActions(content.actions, components),
components,
}
})
}
const onBallDrop = (refBounds: DOMRect) => {
const onBallMoved = (refBounds: DOMRect) => {
if (!isBoundsOnCourt(refBounds)) {
removeCourtBall()
return
}
const playerCollidedIdx = getPlayerCollided(refBounds, content.players)
const playerCollidedIdx = getComponentCollided(
refBounds,
content.components,
)
if (playerCollidedIdx != -1) {
setContent((content) => {
return {
...content,
players: content.players.map((player) => ({
...player,
hasBall: false,
})),
components: content.components.map((c) =>
c.type == "player"
? {
...c,
hasBall: false,
}
: c,
),
}
})
onBallDropOnPlayer(playerCollidedIdx)
onBallDropOnComponent(playerCollidedIdx)
return
}
if (content.objects.findIndex((o) => o.type == "ball") != -1) {
if (content.components.findIndex((o) => o.type == "ball") != -1) {
return
}
const courtBounds = courtDivContentRef.current!.getBoundingClientRect()
const { x, y } = ratioWithinBase(refBounds, courtBounds)
let courtObject: CourtObject
courtObject = {
type: "ball",
const courtObject = {
type: BALL_TYPE,
id: BALL_ID,
rightRatio: x,
bottomRatio: y,
}
} as Ball
let components = content.components.map((c) =>
c.type == "player"
? {
...c,
hasBall: false,
}
: c,
)
components = [...components, courtObject]
const players = content.players.map((player) => ({
...player,
hasBall: false,
setContent((content) => ({
...content,
actions: updateActions(content.actions, components),
components,
}))
setContent((content) => {
return {
...content,
actions: updateActions(content.actions, players),
players,
objects: [...content.objects, courtObject],
}
})
}
const removePlayer = (player: Player) => {
setContent((content) => ({
...content,
players: toSplicedPlayers(content.players, player, false),
objects: [...content.objects],
components: replaceOrInsert(content.components, player, false),
actions: content.actions.filter(
(a) =>
a.toPlayerId !== player.id && a.fromPlayerId !== player.id,
(a) => a.toId !== player.id && a.fromId !== player.id,
),
}))
let setter
@ -379,14 +376,21 @@ function EditorView({
const removeCourtBall = () => {
setContent((content) => {
const ballObj = content.objects.findIndex((o) => o.type == "ball")
const ballObj = content.components.findIndex(
(o) => o.type == "ball",
)
const components = content.components.map((c) =>
c.type == "player"
? ({
...c,
hasBall: false,
} as Player)
: c,
)
components.splice(ballObj, 1)
return {
...content,
players: content.players.map((player) => ({
...player,
hasBall: false,
})),
objects: content.objects.toSpliced(ballObj, 1),
components,
}
})
setObjects([{ key: "ball" }])
@ -423,7 +427,7 @@ function EditorView({
canDetach={(div) =>
isBoundsOnCourt(div.getBoundingClientRect())
}
onElementDetached={onPieceDetach}
onElementDetached={onRackPieceDetach}
render={({ team, key }) => (
<PlayerPiece
team={team}
@ -441,7 +445,7 @@ function EditorView({
canDetach={(div) =>
isBoundsOnCourt(div.getBoundingClientRect())
}
onElementDetached={onObjectDetach}
onElementDetached={onRackedObjectDetach}
render={renderCourtObject}
/>
@ -452,7 +456,7 @@ function EditorView({
canDetach={(div) =>
isBoundsOnCourt(div.getBoundingClientRect())
}
onElementDetached={onPieceDetach}
onElementDetached={onRackPieceDetach}
render={({ team, key }) => (
<PlayerPiece
team={team}
@ -466,16 +470,14 @@ function EditorView({
<div id="court-div">
<div id="court-div-bounds">
<BasketCourt
players={content.players}
objects={content.objects}
components={content.components}
actions={content.actions}
onBallMoved={onBallDrop}
onBallMoved={onBallMoved}
courtImage={<Court courtType={courtType} />}
courtRef={courtDivContentRef}
setActions={(actions) =>
setContent((content) => ({
...content,
players: content.players,
actions: actions(content.actions),
}))
}
@ -515,8 +517,8 @@ function EditorView({
}
setContent((content) => ({
...content,
players: toSplicedPlayers(
content.players,
components: replaceOrInsert(
content.components,
player,
true,
),
@ -533,10 +535,11 @@ function EditorView({
}
function isBallOnCourt(content: TacticContent) {
if (content.players.findIndex((p) => p.hasBall) != -1) {
return true
}
return content.objects.findIndex((o) => o.type == "ball") != -1
return (
content.components.findIndex(
(c) => (c.type == "player" && c.hasBall) || c.type == BALL_TYPE,
) != -1
)
}
function renderCourtObject(courtObject: RackedCourtObject) {
@ -558,12 +561,18 @@ function Court({ courtType }: { courtType: string }) {
)
}
function getRackPlayers(team: PlayerTeam, players: Player[]): RackedPlayer[] {
function getRackPlayers(
team: PlayerTeam,
components: TacticComponent[],
): RackedPlayer[] {
return ["1", "2", "3", "4", "5"]
.filter(
(role) =>
players.findIndex((p) => p.team == team && p.role == role) ==
-1,
components.findIndex(
(c) =>
c.type == "player" && c.team == team && c.role == role,
) == -1,
)
.map((key) => ({ team, key }))
}
@ -611,14 +620,11 @@ function useContentState<S>(
return [content, setContentSynced, savingState]
}
function toSplicedPlayers(
players: Player[],
player: Player,
function replaceOrInsert<A extends TacticComponent>(
array: A[],
it: A,
replace: boolean,
): Player[] {
const idx = players.findIndex(
(p) => p.team === player.team && p.role === player.role,
)
return players.toSpliced(idx, 1, ...(replace ? [player] : []))
): A[] {
const idx = array.findIndex((i) => i.id == it.id)
return array.toSpliced(idx, 1, ...(replace ? [it] : []))
}

@ -46,7 +46,7 @@ export function CourtAction({
}}
wavy={action.type == ActionKind.DRIBBLE}
//TODO place those magic values in constants
endRadius={action.toPlayerId ? 26 : 17}
endRadius={action.toId ? 26 : 17}
startRadius={0}
onDeleteRequested={onActionDeleted}
style={{

@ -17,7 +17,7 @@ export function Header({ username }: { username: string }) {
location.pathname = BASE + "/"
}}>
<span id="IQ">IQ</span>
<span id="Ball">Ball</span>
<span id="Ball">CourtObjects</span>
</h1>
</div>
<div id="header-right">

@ -26,7 +26,7 @@ CREATE TABLE Tactic
name varchar NOT NULL,
creation_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
owner integer NOT NULL,
content varchar DEFAULT '{"players": [], "actions": [], "objects": []}' NOT NULL,
content varchar DEFAULT '{"components": [], "actions": []}' NOT NULL,
court_type varchar CHECK ( court_type IN ('HALF', 'PLAIN')) NOT NULL,
FOREIGN KEY (owner) REFERENCES Account
);

@ -42,7 +42,7 @@ class EditorController {
return ViewHttpResponse::react("views/Editor.tsx", [
"id" => -1, //-1 id means that the editor will not support saves
"name" => TacticModel::TACTIC_DEFAULT_NAME,
"content" => '{"players": [], "objects": [], "actions": []}',
"content" => '{"components": [], "actions": []}',
"courtType" => $courtType->name(),
]);
}

@ -52,7 +52,7 @@
<body>
<button onclick="location.pathname='{{ path('/disconnect') }}'"> Se déconnecter</button>
<div id="bandeau">
<h1>IQ Ball</h1>
<h1>IQ CourtObjects</h1>
<div id="account" onclick="location.pathname='{{ path('/settings') }}'">
<img
src="{{ path('/assets/icon/account.svg') }}"

Loading…
Cancel
Save