Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 70 additions & 9 deletions src/components/Common/PlaySetupModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import {
} from 'src/types'
import { ModalContext } from 'src/contexts'
import { ModalContainer } from './ModalContainer'
import { Slider } from './Slider'
import {
customToPresetTimeControl,
parseTimeControl,
getPresetOptions,
} from 'src/lib/timeControlUtils'

const maiaOptions = [
'maia_kdd_1100',
Expand Down Expand Up @@ -82,6 +88,9 @@ export const PlaySetupModal: React.FC<Props> = (props: Props) => {
const [timeControl, setTimeControl] = useState<TimeControl>(
props.timeControl || TimeControlOptions[0],
)
const [useCustomTime, setUseCustomTime] = useState<boolean>(false)
const [customMinutes, setCustomMinutes] = useState<number>(10)
const [customIncrement, setCustomIncrement] = useState<number>(0)
const [isBrain, setIsBrain] = useState<boolean>(props.isBrain || false)
const [sampleMoves, setSampleMoves] = useState<boolean>(
props.sampleMoves || true,
Expand All @@ -102,9 +111,18 @@ export const PlaySetupModal: React.FC<Props> = (props: Props) => {

const [openMoreOptions, setMoreOptionsOpen] = useState<boolean>(true)

// Helper function to get the effective time control
const getEffectiveTimeControl = (): TimeControl => {
if (useCustomTime) {
return customToPresetTimeControl(customMinutes, customIncrement)
}
return timeControl
}

const start = useCallback(
(color: Color | undefined) => {
const player = color ?? ['white', 'black'][Math.floor(Math.random() * 2)]
const effectiveTimeControl = getEffectiveTimeControl()

if (fen && !new Chess().validateFen(fen).valid) {
toast.error('Invalid Starting FEN provided')
Expand All @@ -120,7 +138,7 @@ export const PlaySetupModal: React.FC<Props> = (props: Props) => {
player: player,
//maiaPartnerVersion: maiaPartnerVersion,
maiaVersion: maiaVersion,
timeControl: timeControl,
timeControl: effectiveTimeControl,
sampleMoves: sampleMoves,
simulateMaiaTime: simulateMaiaTime,
startFen: fen,
Expand All @@ -133,7 +151,7 @@ export const PlaySetupModal: React.FC<Props> = (props: Props) => {
player: player,
maiaPartnerVersion: maiaPartnerVersion,
maiaVersion: maiaVersion,
timeControl: timeControl,
timeControl: effectiveTimeControl,
isBrain: isBrain,
sampleMoves: sampleMoves,
simulateMaiaTime: simulateMaiaTime,
Expand All @@ -148,7 +166,7 @@ export const PlaySetupModal: React.FC<Props> = (props: Props) => {
push,
maiaPartnerVersion,
maiaVersion,
timeControl,
getEffectiveTimeControl,
sampleMoves,
simulateMaiaTime,
fen,
Expand Down Expand Up @@ -250,18 +268,61 @@ export const PlaySetupModal: React.FC<Props> = (props: Props) => {
<div>
<label
htmlFor="time-control-select"
className="mb-1 block text-sm font-medium text-primary"
className="mb-2 block text-sm font-medium text-primary"
>
Time control:
</label>
<div id="time-control-select">

{/* Toggle between preset and custom */}
<div className="mb-3">
<OptionSelect
options={TimeControlOptions}
labels={TimeControlOptionNames}
selected={timeControl}
onChange={setTimeControl}
options={[false, true]}
labels={['Preset', 'Custom']}
selected={useCustomTime}
onChange={setUseCustomTime}
/>
</div>

{useCustomTime ? (
/* Custom time controls with sliders */
<div className="space-y-3 rounded bg-background-2 p-3">
<Slider
id="time-slider"
label="Time per side"
value={customMinutes}
min={1}
max={180}
step={1}
unit=" min"
onChange={setCustomMinutes}
/>
<Slider
id="increment-slider"
label="Increment per move"
value={customIncrement}
min={0}
max={60}
step={1}
unit=" sec"
onChange={setCustomIncrement}
/>
<div className="mt-2 text-center">
<span className="text-xs text-secondary">
Time control: {customMinutes}+{customIncrement}
</span>
</div>
</div>
) : (
/* Preset time controls */
<div id="time-control-select">
<OptionSelect
options={TimeControlOptions}
labels={TimeControlOptionNames}
selected={timeControl}
onChange={setTimeControl}
/>
</div>
)}
</div>

<div>
Expand Down
81 changes: 81 additions & 0 deletions src/components/Common/Slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react'

interface SliderProps {
label: string
value: number
min: number
max: number
step?: number
unit?: string
onChange: (value: number) => void
id?: string
}

export const Slider: React.FC<SliderProps> = ({
label,
value,
min,
max,
step = 1,
unit = '',
onChange,
id,
}) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange(Number(e.target.value))
}

return (
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<label htmlFor={id} className="text-sm font-medium text-primary">
{label}
</label>
<span className="text-sm text-secondary">
{value}
{unit}
</span>
</div>
<div className="relative">
<input
id={id}
type="range"
min={min}
max={max}
step={step}
value={value}
onChange={handleChange}
className="h-2 w-full cursor-pointer appearance-none rounded-lg bg-background-3 outline-none"
style={{
background: `linear-gradient(to right, rgb(var(--color-human-accent4)) 0%, rgb(var(--color-human-accent4)) ${
((value - min) / (max - min)) * 100
}%, rgb(var(--color-background3)) ${
((value - min) / (max - min)) * 100
}%, rgb(var(--color-background3)) 100%)`,
}}
/>
<style jsx>{`
input[type='range']::-webkit-slider-thumb {
appearance: none;
height: 18px;
width: 18px;
border-radius: 50%;
background: rgb(var(--color-human-accent4));
cursor: pointer;
border: 2px solid rgb(var(--color-background1));
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
input[type='range']::-moz-range-thumb {
height: 18px;
width: 18px;
border-radius: 50%;
background: rgb(var(--color-human-accent4));
cursor: pointer;
border: 2px solid rgb(var(--color-background1));
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
`}</style>
</div>
</div>
)
}
1 change: 1 addition & 0 deletions src/components/Common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export * from './ModalContainer'
export * from './ContinueAgainstMaia'
export * from './AnimatedNumber'
export * from './DownloadModelModal'
export * from './Slider'
63 changes: 63 additions & 0 deletions src/lib/timeControlUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Utility functions for time control conversion between custom values and preset formats
*/

import { TimeControl, TimeControlOptions } from 'src/types'

export interface CustomTimeControl {
minutes: number
increment: number
}

/**
* Convert custom time control values to the closest preset TimeControl format
*/
export const customToPresetTimeControl = (
minutes: number,
increment: number,
): TimeControl => {
const customFormat = `${minutes}+${increment}`

// Check if it matches any existing preset
if (TimeControlOptions.includes(customFormat as TimeControl)) {
return customFormat as TimeControl
}

// For custom values that don't match presets, return the custom format
// The game logic will need to handle this appropriately
return customFormat as TimeControl
}

/**
* Parse a TimeControl string into custom time control values
*/
export const parseTimeControl = (
timeControl: TimeControl,
): CustomTimeControl => {
if (timeControl === 'unlimited') {
return { minutes: 0, increment: 0 }
}

const [minutesStr, incrementStr] = timeControl.split('+')
return {
minutes: parseInt(minutesStr, 10) || 0,
increment: parseInt(incrementStr, 10) || 0,
}
}

/**
* Check if a time control is a preset option
*/
export const isPresetTimeControl = (timeControl: TimeControl): boolean => {
return TimeControlOptions.includes(timeControl)
}

/**
* Get preset time control options with labels
*/
export const getPresetOptions = () => {
return TimeControlOptions.map((option) => ({
value: option,
label: option === 'unlimited' ? 'Unlimited' : option,
}))
}
Loading