Skip to content

Commit

Permalink
added score comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
kdblocher committed Aug 6, 2023
1 parent fc96889 commit 430ae82
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 22 deletions.
37 changes: 37 additions & 0 deletions src/components/core/BidSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { readonlyRecord as RR, readonlyArray as RA, } from "fp-ts"
import { ContractBid, eqContractBid, levels, strains } from "../../model/bridge"
import StrainTableView, { StrainSpan } from "./StrainTableView"
import { pipe } from "fp-ts/lib/function"
import { Button } from '@fluentui/react-components';

interface BidSelectorButtonProps {
selected: boolean,
children: JSX.Element
setSelected: (selected: boolean) => void
}
const BidSelectorButton = ({ children, selected, setSelected }: BidSelectorButtonProps) =>
<Button style={{minWidth: "50px", width: "50px"}} onClick={() => setSelected(!selected)} appearance={selected ? "primary" : "secondary"}>{children}</Button>

interface BidSelectorProps {
bids: ReadonlyArray<ContractBid>,
setSelectedBid: (bid: ContractBid, selected: boolean) => void
}
export const BidSelector = ({ bids, setSelectedBid }: BidSelectorProps) => {
const contractBidsEnabledByLevel =
pipe(levels,
RA.map(level => pipe(strains,
RA.map(strain => ({ level, strain }) as ContractBid),
RA.map(bid => [bid.strain, pipe(bids, RA.exists(selectedBid => eqContractBid.equals(bid, selectedBid)))] as const),
RR.fromEntries)))
return (<StrainTableView
table={{rows: contractBidsEnabledByLevel}}
renderColHeader={() => undefined}
renderRowHeader={() => undefined}
renderCell={(selected, s, i) =>
<BidSelectorButton selected={selected} setSelected={selected => setSelectedBid({ level: i + 1, strain: s}, selected)}>
<>{i + 1}<StrainSpan className={s} /></>
</BidSelectorButton>}
/>)
}

export default BidSelector
48 changes: 48 additions & 0 deletions src/components/core/StrainTableView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import styled from 'styled-components';
import { readonlyRecord as RR } from 'fp-ts';
import { Strain, strains } from '../../model/bridge';

const suitBase = `
&.S::before { content: "♠"; color: #0000FF }
&.H::before { content: "♥"; color: #FF0000 }
&.D::before { content: "♦"; color: #FFA500 }
&.C::before { content: "♣"; color: #32CD32 }
`

export const StrainSpan = styled.span `
${suitBase}
&.N::before { content: "NT"; color: #000000; font-size: 12px; }
`

type StrainRow<T> = RR.ReadonlyRecord<Strain, T>
export interface StrainTable<T> {
rows: ReadonlyArray<StrainRow<T>>
}

interface StrainTableProps<T> {
table: StrainTable<T>
renderColHeader: ((strain: Strain, index: number) => JSX.Element | undefined)
renderRowHeader: (row: StrainRow<T>, index: number) => JSX.Element | undefined
renderCell: (value: T, strain: Strain, rowIndex: number) => JSX.Element | undefined
}

export const StrainTableView = <T extends any>({ table, renderColHeader, renderRowHeader, renderCell }: StrainTableProps<T>) => {
return (
<table>
<thead>
<tr>
<th></th>
{strains.map((s, i) => renderColHeader ? renderColHeader(s, i) : <th style={{fontWeight: "normal", verticalAlign: "middle"}} key={i}><StrainSpan className={s} /></th>)}
</tr>
</thead>
<tbody>
{table.rows.map((r, i) => <tr key={i}>
<td>{renderRowHeader(r, i)}</td>
{strains.map((s, j) => <td key={j}>{renderCell(r[s], s, i)}</td>)}
</tr>)}
</tbody>
</table>
)
}

export default StrainTableView
57 changes: 57 additions & 0 deletions src/components/stats/ScoreComparison.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { ContractBid, Direction, eqContractBid, ordContractBid } from "../../model/bridge";
import { readonlyArray as RA, readonlyNonEmptyArray as RNEA } from "fp-ts";
import { Scores, compareScores } from "../../model/stats"
import Percentage from "../core/Percentage";
import BidSelector from "../core/BidSelector";
import { useCallback, useMemo, useState } from "react";
import { pipe } from "fp-ts/lib/function";
import styled from "styled-components";
import { BidView } from "../core/BidPath";
import { serializedBidL } from "../../model/serialization";

interface ScoreComparisonProps {
contractBid: ContractBid
scores: Scores
}
const defaultDir : Direction = "N"

export const Columns = styled.div `
display: flex;
flex-direction: row;
`

export const Column = styled.div `
width: 35%;
`

export const ScoreList = styled.ul `
list-style-type: none;
`

const ScoreComparison = ({ contractBid, scores }: ScoreComparisonProps) => {
const [bids, setBids] = useState<RNEA.ReadonlyNonEmptyArray<ContractBid>>([contractBid])
const setSelectedBid = useCallback((bid: ContractBid, selected: boolean) =>
setBids(pipe(
bids,
selected ? RA.union(eqContractBid)([bid]) : RA.filter(b => !eqContractBid.equals(bid, b)),
RA.append(contractBid),
RNEA.uniq(eqContractBid),
RNEA.sort(ordContractBid)))
, [bids, contractBid])
const comparison = useMemo(() => compareScores(scores)(pipe(bids, RNEA.map(b => ([defaultDir, b])))), [bids, scores])
const length = scores.length
return (<section>
<h4>Compare Contracts</h4>
<Columns>
<BidSelector bids={bids} setSelectedBid={setSelectedBid} />
<ScoreList>{length && Object.entries(comparison).map(([contract, count], i) =>
<li key={i}>
{contract === "tie" ? "(ties)" : <BidView bid={serializedBidL.reverseGet(contract)} />}
: {count} (<Percentage numerator={count} denominator={length} />)
</li>)}
</ScoreList>
</Columns>
</section>)
}

export default ScoreComparison
37 changes: 34 additions & 3 deletions src/components/stats/StatsDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import { option as O, readonlyRecord as RR } from "fp-ts";
import {
option as O,
readonlyRecord as RR,
readonlyNonEmptyArray as RNEA,
} from "fp-ts";
import { flow, pipe } from "fp-ts/lib/function";
import { get } from "../../lib/object";
import { Generation } from "../../model/job";
import { SerializedBidPath } from "../../model/serialization";
import {
SerializedBidPath,
serializedBidPathL,
} from "../../model/serialization";
import SolutionStats from "./SolutionStats";
import ScoreComparison from "./ScoreComparison";
import { ContractBid } from "../../model/bridge";
import styled from "styled-components";

export const Columns = styled.div`
display: flex;
flex-direction: row;
`;

export const Column = styled.div`
width: 35%;
`;

interface StatsDetailsProps {
path: SerializedBidPath;
Expand All @@ -24,10 +43,22 @@ const StatsDetails = ({ path, generation, onClose }: StatsDetailsProps) => {
O.chain(O.fromPredicate((len) => len > 0)),
O.toNullable
);
const contractBid = pipe(
path,
serializedBidPathL.reverseGet,
RNEA.last
) as ContractBid;
return (
<div>
{solveCount && <h3>{solveCount} solutions found</h3>}
{stats && <SolutionStats stats={stats} />}
<Columns>
<Column>{stats && <SolutionStats stats={stats} />}</Column>
<div>
{stats?.scores && (
<ScoreComparison contractBid={contractBid} scores={stats.scores} />
)}
</div>
</Columns>
</div>
);
};
Expand Down
15 changes: 10 additions & 5 deletions src/model/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
readonlyTuple,
refinement,
semigroup,
eq,
} from "fp-ts";
import { constant, flow, pipe } from "fp-ts/lib/function";
import { ordAscending, ordDescending } from "../lib";
Expand All @@ -17,6 +18,7 @@ import {
contractBids,
ContractModifier,
Direction,
eqContractBid,
getIsVulnerable,
ordContractBid,
Strain,
Expand Down Expand Up @@ -74,7 +76,11 @@ export const transpose = <K1 extends string, K2 extends string, T>(
)
);

type OptimalBid = ContractBid | "Pass";
export type OptimalBid = ContractBid | "Pass";
export const eqOptimalBid: eq.Eq<OptimalBid> = {
equals: (a, b) =>
(!(a === "Pass" || b === "Pass") && eqContractBid.equals(a, b)) || a === b,
};
const initialContractBid: refinement.Refinement<OptimalBid, ContractBid> = (
b
): b is ContractBid => b !== "Pass";
Expand All @@ -83,10 +89,9 @@ const initialBids = pipe(
readonlyArray.sort(ordContractBid),
readonlyArray.prepend<OptimalBid>("Pass")
);
const ordInitialBidsAscending = ordAscending(initialBids);
const ordInitialBidsDescending = ordDescending(initialBids);

type ContractScorePair = readonly [OptimalBid, Score];
export type ContractScorePair = readonly [OptimalBid, Score];
const getDirectionScores =
(counts: TrickCountsByStrain) => (isVulnerable: boolean) =>
pipe(
Expand All @@ -110,8 +115,8 @@ const getDirectionScores =
)
);

const getAllScores =
(table: TrickCountsByDirectionThenStrain) => (vulnerability: Vulnerability) =>
export const getAllScores =
(vulnerability: Vulnerability) => (table: TrickCountsByDirectionThenStrain) =>
pipe(
table,
RR.mapWithIndex((direction, counts) =>
Expand Down
3 changes: 2 additions & 1 deletion src/model/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,10 @@ export const ordContractBid: ord.Ord<ContractBid> = ord
ord.contramap((c) => c.strain)
)
);
export const levels = RNEA.makeBy((level) => level + 1)(7);
export const contractBids: ReadonlyArray<ContractBid> = pipe(
apply.sequenceS(RA.Apply)({
level: RA.makeBy(7, (level) => level + 1),
level: levels,
strain: strains,
}),
RA.sort(ordContractBid)
Expand Down
Loading

0 comments on commit 430ae82

Please sign in to comment.