Skip to content

Commit

Permalink
Docstrings for all methods in util/*, as well as some associated clea…
Browse files Browse the repository at this point in the history
…nup and reorganization (#1625)

* util/common.ts

* cards/utils

* oops

* util/decks

* util/browser

* fix

* 4 left

* util/formats

* util/firebase

* util/cards

* util/game

* minor
  • Loading branch information
AlexNisnevich authored Oct 1, 2022
1 parent 8fbf420 commit e30d06d
Show file tree
Hide file tree
Showing 29 changed files with 523 additions and 304 deletions.
42 changes: 21 additions & 21 deletions src/common/components/cards/ActiveDeck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import * as React from 'react';

import { MAX_Z_INDEX, TYPE_EVENT, TYPE_ROBOT, TYPE_STRUCTURE } from '../../constants';
import * as w from '../../types';
import { groupCards, selectType } from '../../util/cards';
import Tooltip from '../Tooltip';
import MustBeLoggedIn from '../users/MustBeLoggedIn';

import { groupCards, selectType } from './utils';
import ActiveDeckCard from './ActiveDeckCard';
import DeckValidationIndicator from './DeckValidationIndicator';
import { CardWithCount } from './types';
Expand Down Expand Up @@ -91,7 +91,7 @@ export default class ActiveDeck extends React.Component<ActiveDeckProps, ActiveD
}}
>
{isASet ? 'Set' : 'Deck'} [
<span style={{color: this.hasRightCardCount ? 'green' : 'red'}}>
<span style={{ color: this.hasRightCardCount ? 'green' : 'red' }}>
{cards.length}
</span>
{' '}/{' '}
Expand All @@ -116,7 +116,7 @@ export default class ActiveDeck extends React.Component<ActiveDeckProps, ActiveD
{
!isASet && (
<div style={{ float: 'right' }}>
<DeckValidationIndicator hideNumCards cards={cards} deck={deck} set={setForDeck} />
<DeckValidationIndicator hideNumCards cards={cards} deck={deck} set={setForDeck} />
</div>
)
}
Expand All @@ -138,12 +138,12 @@ export default class ActiveDeck extends React.Component<ActiveDeckProps, ActiveD

{
setForDeck &&
<TextField
disabled
label="For the set:"
value={`${setForDeck.name} by ${setForDeck.metadata.authorName}`}
style={{ width: '100%', marginBottom: 10 }}
/>
<TextField
disabled
label="For the set:"
value={`${setForDeck.name} by ${setForDeck.metadata.authorName}`}
style={{ width: '100%', marginBottom: 10 }}
/>
}

<div>
Expand All @@ -162,22 +162,22 @@ export default class ActiveDeck extends React.Component<ActiveDeckProps, ActiveD
{
cards.length > 0
? <React.Fragment>
{this.renderSaveButton()}
<div style={{ margin: '20px auto' }}>
{this.renderCardList()}
</div>
{this.renderSaveButton()}
</React.Fragment>
{this.renderSaveButton()}
<div style={{ margin: '20px auto' }}>
{this.renderCardList()}
</div>
{this.renderSaveButton()}
</React.Fragment>
: this.renderSaveButton()
}
</div>
);
}

private handleChangeName = (e: React.SyntheticEvent<any>) => { this.setState({name: e.currentTarget.value}); };
private handleChangeDescription = (e: React.SyntheticEvent<any>) => { this.setState({description: e.currentTarget.value}); };
private handleGroupByCost = () => { this.setState({grouping: 0}); };
private handleGroupByType = () => { this.setState({grouping: 1}); };
private handleChangeName = (e: React.SyntheticEvent<any>) => { this.setState({ name: e.currentTarget.value }); };
private handleChangeDescription = (e: React.SyntheticEvent<any>) => { this.setState({ description: e.currentTarget.value }); };
private handleGroupByCost = () => { this.setState({ grouping: 0 }); };
private handleGroupByType = () => { this.setState({ grouping: 1 }); };

private handleSave = () => {
const { id, cards, isASet, onSave } = this.props;
Expand Down Expand Up @@ -211,7 +211,7 @@ export default class ActiveDeck extends React.Component<ActiveDeckProps, ActiveD
const handleClick = [this.handleGroupByCost, this.handleGroupByType][grouping];

return (
<div style={{width: '47.5%'}}>
<div style={{ width: '47.5%' }}>
<Tooltip text={tooltip} place="top" style={{ zIndex: MAX_Z_INDEX }}>
<Icon
className="material-icons"
Expand All @@ -231,7 +231,7 @@ export default class ActiveDeck extends React.Component<ActiveDeckProps, ActiveD

private renderCard(card: CardWithCount, idx: number): JSX.Element {
return (
<div key={idx} style={{position: 'relative'}}>
<div key={idx} style={{ position: 'relative' }}>
<ActiveDeckCard
card={card}
showCount={!this.props.isASet}
Expand Down
14 changes: 7 additions & 7 deletions src/common/components/cards/DeckSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import * as React from 'react';

import { TYPE_EVENT, TYPE_ROBOT, TYPE_STRUCTURE } from '../../constants';
import * as w from '../../types';
import { groupCards } from '../../util/cards';
import ButtonInRow from '../ButtonInRow';
import CardTooltip from '../card/CardTooltip';
import InlineCardCostBadge from '../card/InlineCardCostBadge';
import MustBeLoggedIn from '../users/MustBeLoggedIn';

import { groupCards } from './utils';
import DeckValidationIndicator from './DeckValidationIndicator';
import { CardWithCount } from './types';

Expand Down Expand Up @@ -62,10 +62,10 @@ export default class DeckSummary extends React.Component<DeckSummaryProps> {
return (
<Paper
key={deck.name}
style={{maxWidth: 490, marginRight: 20, marginBottom: 20, padding: 10}}
style={{ maxWidth: 490, marginRight: 20, marginBottom: 20, padding: 10 }}
>
<div style={{display: 'flex', marginBottom: 15}}>
<div style={{fontSize: 32, fontWeight: 100}}>
<div style={{ display: 'flex', marginBottom: 15 }}>
<div style={{ fontSize: 32, fontWeight: 100 }}>
{deck.name}
</div>

Expand Down Expand Up @@ -118,8 +118,8 @@ export default class DeckSummary extends React.Component<DeckSummaryProps> {
/>
</MustBeLoggedIn>

<div style={{padding: '0 10px 10px 10px'}}>
<div style={{float: 'left', marginRight: 30}}>
<div style={{ padding: '0 10px 10px 10px' }}>
<div style={{ float: 'left', marginRight: 30 }}>
<h4
style={{
margin: '20px 0 20px -10px'
Expand All @@ -130,7 +130,7 @@ export default class DeckSummary extends React.Component<DeckSummaryProps> {
{this.renderCards(robots)}
</div>

<div style={{float: 'left'}}>
<div style={{ float: 'left' }}>
<h4
style={{
margin: '20px 0 20px -10px'
Expand Down
8 changes: 4 additions & 4 deletions src/common/components/cards/SetSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import * as React from 'react';
import * as CopyToClipboard from 'react-copy-to-clipboard';

import * as w from '../../types';
import { sortCards } from '../../util/cards';
import { SetDraftFormat } from '../../util/formats';
import { Card } from '../card/Card';
import Tooltip from '../Tooltip';
import MustBeLoggedIn from '../users/MustBeLoggedIn';
import ProfileLink from '../users/ProfileLink';

import { sortCards } from './utils';
import { SortCriteria } from './types.enums';

interface SetSummaryBaseProps {
Expand Down Expand Up @@ -171,17 +171,17 @@ class SetSummary extends React.Component<SetSummaryProps, SetSummaryState> {
</div>
{isCardListExpanded &&
<div>
<div style={{clear: 'both'}}/>
<div style={{ clear: 'both' }} />
{
cards
.sort((c1, c2) => sortCards(c1, c2, SortCriteria.Cost))
.map((card, idx) => (
<div key={idx} style={{float: 'left'}}>
<div key={idx} style={{ float: 'left' }}>
{Card.fromObj(card, { scale: 0.7, onCardClick: () => { this.handleClickCard(card); } })}
</div>
))
}
<div style={{clear: 'both'}}/>
<div style={{ clear: 'both' }} />
</div>
}
<div className={classes.numDecksCreated}>
Expand Down
9 changes: 9 additions & 0 deletions src/common/components/cards/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as w from '../../types';
import { Layout, SortCriteria, SortOrder } from './types.enums';

export type FilterKey = 'robots' | 'events' | 'structures';
export type Filters = Record<FilterKey, boolean>;

export type CardWithCount = w.CardInStore & { count: number };

Expand All @@ -17,3 +18,11 @@ export interface DeckCreationProperties {
selectedCardIds: w.CardId[]
layout: Layout
}

export interface CardDisplayOpts {
filters: Filters
costRange: [number, number]
searchText: string
sortCriteria: SortCriteria
sortOrder: SortOrder
}
83 changes: 83 additions & 0 deletions src/common/components/cards/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/** Utility methods pertaining specifically to functionality of cards-related components go here. */

import { countBy, uniqBy } from 'lodash';

import { typeToString, TYPE_EVENT, TYPE_ROBOT, TYPE_STRUCTURE } from '../../constants';
import * as w from '../../types';

import { CardDisplayOpts, Filters } from './types';
import { SortCriteria, SortOrder } from './types.enums';

/** Given an array of cards, group by card id and return an array of cards with corresponding counts. */
export function groupCards(cards: w.CardInStore[]): Array<w.CardInStore & { count: number }> {
return uniqBy(cards, 'id').map((card) =>
({ ...card, count: countBy(cards, 'name')[card.name] })
);
}

/** Given an array of cards, return those matching a given card type. */
export function selectType(cards: w.CardInStore[], type: w.CardType): w.CardInStore[] {
return cards.filter((card) => card.type === type);
}

/** Given an array of cards and CardDisplayOpts, filter and sort the array as specified by the opts. */
export function getDisplayedCards(cards: w.CardInStore[], opts: CardDisplayOpts): w.CardInStore[] {
return cards
.filter((card) => isCardVisible(card, opts.filters, opts.costRange) && searchCards(card, opts.searchText))
.sort((c1, c2) => sortCards(c1, c2, opts.sortCriteria, opts.sortOrder));
}

/** Return whether a given card matches the given types filters and cost range filter. */
export function isCardVisible(card: w.CardInStore, filters: Filters, costRange: [number, number]): boolean {
if ((!filters.robots && card.type === TYPE_ROBOT) ||
(!filters.events && card.type === TYPE_EVENT) ||
(!filters.structures && card.type === TYPE_STRUCTURE) ||
(card.cost < costRange[0] || card.cost > costRange[1])) {
return false;
} else {
return true;
}
}

/** Return whether a given card matches the given search query. */
function searchCards(card: w.CardInStore, query = ''): boolean {
query = query.toLowerCase();
return card.name.toLowerCase().includes(query) || (card.text || '').toLowerCase().includes(query);
}

/** Custom sort function for `CardInStore`s that takes a SortCriteria and SortOrder and performs the given sort. */
export function sortCards(c1: w.CardInStore, c2: w.CardInStore, criteria: SortCriteria, order: SortOrder = 0): 1 | 0 | -1 {
// Individual sort columns that are composed into sort functions below.
// (Note: We convert numbers to base-36 to preserve sorting. eg. "10" < "9" but "a" > "9".)
const [timestamp, cost, name, type, attack, health, speed] = [
// we want timestamp to be sorted backwards compared to other fields.
// also, created cards without a timestamp should still come before builtin cards.
(c: w.CardInStore) => (9999999999999 - (c.metadata.updated || (c.metadata.source.type === 'builtin' ? 0 : 1))).toString(36),
(c: w.CardInStore) => c.cost.toString(36),
(c: w.CardInStore) => c.name.toLowerCase(),
(c: w.CardInStore) => typeToString(c.type),
(c: w.CardInStore) => (c.stats?.attack || 0).toString(36),
(c: w.CardInStore) => (c.stats?.health || 0).toString(36),
(c: w.CardInStore) => (c.stats?.speed || 0).toString(36)
];

// Sorting functions for card collections:
// 0 = timestamp, 1 = cost, 2 = name, 3 = type, 4 = attack, 5 = health, 6 = speed.
const f = [
(c: w.CardInStore) => [timestamp(c), cost(c), name(c)],
(c: w.CardInStore) => [cost(c), name(c)],
(c: w.CardInStore) => [name(c), cost(c)],
(c: w.CardInStore) => [type(c), cost(c), name(c)],
(c: w.CardInStore) => [attack(c), cost(c), name(c)],
(c: w.CardInStore) => [health(c), cost(c), name(c)],
(c: w.CardInStore) => [speed(c), cost(c), name(c)]
][criteria];

if (f(c1) < f(c2)) {
return order ? 1 : -1;
} else if (f(c1) > f(c2)) {
return order ? -1 : 1;
} else {
return 0;
}
}
2 changes: 1 addition & 1 deletion src/common/components/play/DeckPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Select from '@material-ui/core/Select';
import * as React from 'react';

import * as w from '../../types';
import { cardsInDeck } from '../../util/cards';
import { cardsInDeck } from '../../util/decks';
import EnergyCurve from '../cards/EnergyCurve';

interface DeckPickerProps {
Expand Down
4 changes: 2 additions & 2 deletions src/common/components/play/Lobby.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as React from 'react';
import * as m from '../../../server/multiplayer/multiplayer';
import { CHAT_WIDTH } from '../../constants';
import * as w from '../../types';
import { unpackDeck } from '../../util/cards';
import { unpackDeck } from '../../util/decks';
import { GameFormat, renderFormatDisplayName } from '../../util/formats';
import Background from '../Background';
import RouterDialog from '../RouterDialog';
Expand Down Expand Up @@ -117,7 +117,7 @@ export default class Lobby extends React.Component<LobbyProps, LobbyState> {
</div>

<Title text="Arena" />
<div style={{padding: `20px ${CHAT_WIDTH + 20}px 0 20px`}}>
<div style={{ padding: `20px ${CHAT_WIDTH + 20}px 0 20px` }}>
<LobbyStatus
connecting={connecting}
connected={connected}
Expand Down
3 changes: 1 addition & 2 deletions src/common/components/play/PreGameModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import * as React from 'react';

import * as w from '../../types';
import { getQueryString } from '../../util/browser';
import { unpackDeck } from '../../util/cards';
import { sortDecks } from '../../util/decks';
import { sortDecks, unpackDeck } from '../../util/decks';
import { BuiltinOnlyGameFormat, BUILTIN_FORMATS, GameFormat, NormalGameFormat, SetDraftFormat, SetFormat } from '../../util/formats';
import RouterDialog from '../RouterDialog';

Expand Down
Loading

0 comments on commit e30d06d

Please sign in to comment.