File size: 5,669 Bytes
6fd9719
 
af71f44
 
 
 
4c67d79
6fd9719
6bb08c4
 
 
c6ef322
4c67d79
6bb08c4
6fd9719
4c67d79
6371c28
b715c89
ba7c9fe
 
 
 
 
 
 
 
 
6371c28
 
ba7c9fe
6bb08c4
 
6371c28
 
 
 
6bb08c4
 
 
5c4a167
7994c21
3785729
5c4a167
 
4eee6c0
7d70895
 
4eee6c0
 
5c4a167
6371c28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5c4a167
 
6fd9719
 
 
b715c89
 
 
 
 
 
6fd9719
b715c89
 
 
 
 
 
 
 
 
ba7c9fe
 
 
 
b715c89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6371c28
 
 
 
 
 
f9cbedb
6371c28
 
6fd9719
b715c89
 
4eee6c0
 
 
 
6371c28
 
 
4eee6c0
6371c28
b715c89
 
 
 
ba7c9fe
b715c89
5c4a167
6fd9719
 
 
4c67d79
 
6fd9719
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
'use client';

import { Button, ButtonGroup } from '@heroui/button';
import { Card, CardBody } from '@heroui/card';
import { Checkbox } from '@heroui/checkbox';
import { Slider } from '@heroui/slider';
import { useRef, useState } from 'react';

import { useControlContext } from '@/contexts/control-context';

import { Actions } from './consts';
import { rotationController } from './rotation-controller';
import { StateModal, StateModalRef } from './state-modal';

export const UIControls = () => {
  const stateModalRef = useRef<StateModalRef | null>(null);
  const [isSolving, setIsSolving] = useState(false);
  const [isControlsOpen, setIsControlsOpen] = useState(true);
  const {
    rubiksCubeRef,
    showRotationIndicators,
    setShowRotationIndicators,
    setBackground,
    cubeRoughness,
    setCubeRoughness,
    cubeSpeed,
    setCubeSpeed,
    scrambleLength,
    setScrambleLength,
  } = useControlContext();

  const scramble = () => {
    const scrambleSteps = Array.from(
      { length: scrambleLength },
      () => Actions[Math.floor(Math.random() * Actions.length)],
    );
    rubiksCubeRef?.current?.rotate(scrambleSteps);
  };

  const reset = () => {
    rubiksCubeRef?.current?.reset();
    setIsSolving(false);
  };

  const showState = () => {
    const state = rotationController.getState();
    stateModalRef.current?.open(state);
  };

  const train = () => {
    window.open('https://github.com/crossentropy-ai/rlcube', '_blank');
  };

  const solve = async () => {
    try {
      setIsSolving(true);
      const response = await fetch('/api/solve', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ state: rotationController.getState() }),
      });
      if (response.status === 422) {
        alert('Unable to solve the cube.');
        return;
      }
      if (!response.ok) {
        throw new Error('Server error', { cause: response });
      }
      const { steps } = await response.json();
      rotationController.addRotationStepCode(...steps);
    } catch (err) {
      alert('An error occurred. Check the console for details.');
      console.error(err);
    } finally {
      setIsSolving(false);
    }
  };

  return (
    <div className="z-10 pointer-events-none">
      <Card className="max-w-sm bg-white/30 border border-white/80 backdrop-blur-xl pointer-events-auto">
        <CardBody className="flex flex-col">
          <div className="flex justify-between items-center">
            <div className="text-2xl font-bold">Controls</div>
            <Button variant="light" size="sm" onPress={() => setIsControlsOpen(!isControlsOpen)}>
              {isControlsOpen ? 'Hide' : 'Show'}
            </Button>
          </div>
          <div
            className={`
              flex flex-col gap-6 transition-all duration-500 ease-in-out
              ${isControlsOpen ? 'max-h-[1000px] opacity-100' : 'max-h-0 opacity-0 pointer-events-none'}
              overflow-hidden
            `}
            style={{ willChange: 'max-height, opacity' }}
          >
            <div className="flex flex-col gap-2 mt-6">
              <div className="flex items-center justify-between">
                <div className="text-sm">Rotation Indicators</div>
                <Checkbox isSelected={showRotationIndicators} onValueChange={setShowRotationIndicators} />
              </div>
              <div className="flex items-center justify-between">
                <div className="text-sm">Background</div>
                <ButtonGroup size="sm">
                  <Button onPress={() => setBackground('sunset')}>Sunset</Button>
                  <Button onPress={() => setBackground('dawn')}>Dawn</Button>
                  <Button onPress={() => setBackground('forest')}>Forest</Button>
                </ButtonGroup>
              </div>
              <Slider
                size="sm"
                label="Cube Roughness"
                value={cubeRoughness}
                onChange={(value) => setCubeRoughness(value as number)}
                minValue={0.2}
                maxValue={1}
                step={0.01}
              />
              <Slider
                size="sm"
                label="Cube Speed"
                value={cubeSpeed}
                onChange={(value) => setCubeSpeed(value as number)}
                minValue={1}
                maxValue={10}
                step={1}
              />
              <Slider
                size="sm"
                label="Scramble Length"
                value={scrambleLength}
                onChange={(value) => setScrambleLength(value as number)}
                minValue={1}
                maxValue={6}
                step={1}
              />
            </div>
            <div className="flex flex-col gap-2">
              <div className="flex gap-2">
                <ButtonGroup size="sm">
                  <Button onPress={scramble}>Scramble</Button>
                  <Button onPress={reset}>Reset</Button>
                </ButtonGroup>
                <Button variant="light" size="sm" onPress={showState}>
                  Show State
                </Button>

                <Button size="sm" className="ms-auto" color="success" onPress={solve} isLoading={isSolving}>
                  Solve
                </Button>
              </div>
              <div className="text-sm italic font-bold underline text-primary cursor-pointer" onClick={train}>
                Train your own model!
              </div>
            </div>
          </div>
        </CardBody>
      </Card>

      <StateModal ref={stateModalRef} />
    </div>
  );
};