Simple Rubik's Cube timer
- More events for timer status change and solve phase change
- Think more on the constructor api. should it be exposing all of that functionality
- should there be a constructor overload without the opts object?
- Should it return a promise when you call start that will be resolved when the solve ends? this would be useful but also feels like doing too many things
import canCubeTimer, { SOLVING, INSPECTING } from 'can-cube-timer';
const inSeconds = ms => ms / 1000;
// See the full documentation for other options
const timer = canCubeTimer({
onInspectionWarning: timeRemaining => console.log(`You have ${inSeconds(timeRemaining)} seconds remaining!`),
onPenalty: (isDNF, timeRemaining) => isDNF ?
console.log('Maximum allowed time elapsed. Marking as DNF') :
console.log(`15 seconds past, +2 second penalty, ${timeRemaining} seconds remaining until inspection end`,
onTick: (phase, time, timeRemaining) => {
console.log(`Currently ${phase === SOLVING : 'solv' ? 'inspect'}ing!`);
console.log(`Current time is ${inSeconds(time)}`);
// timeRemaining will be null if phase is SOLVING
console.log(`Inspection time remaining ${timeRemaining}`);
},
onDone: ({ inspectionTime, isDNF, penalized, solveTime }) => {
// see api documentation on solveTime
console.log(```
Inspection Time: ${inSeconds(inspectionTime)}
DNF: ${isDNF}
Penalized: ${isPenalized}
Solve time: ${inSeconds(solveTime)}
```);
}
});
timer.start();
// ...
const inspectionPeek = timer.peek();
console.log(`Timer status: ${inspectionPeek.status}`);
console.log(`Timer phase: ${inspectionPeek.phase}`);
console.log(`Current time: ${inspectionPeek.time}`); // This is the current time of the current phase.`);
console.log(`Total time: ${inspectionTime.totalTime}`); // time since the .start() call
const inspectionStats = timer.startSolve(); // See the documentation on .start() and .startSolve();
// TODO: console.logs for inspectionStats
// ...
const solvePeek = timer.status();
console.log(`Timer status: ${solvePeek.status}`);
console.log(`Timer phase: ${solvePeek.phase}`);
console.log(`Current time: ${solvePeek.time}`); // This is the current time of the current phase.`);
console.log(`Total time: ${solvePeek.totalTime}`); // time since the .start() call
const solve = timer.stop(); // timer.stop() returns the same value as the 'done' event listeners
Typescript typing files are provided with the library.
These are the possible values that timer.status
can have.
enum TimerStatus = {
Running = "RUNNING",
Stopped = "STOPPED",
}
enum SolvePhase = {
Inspecting = "INSPECTING",
Solving = "SOLVING",
}
A timer instance emits certain events and lets you subscribe to those event. Event handlers you register
with the timer will be called with the event
objects described here.
enum EventTypes = {
InspectionWarning = "inspectionWarning",
Penalty = "penalty",
Tick = "tick",
Done = "done",
}
WCA rules state that during the 15 seconds of inspection time, the referee will first give a warning at 8 seconds
and another warning at 12 seconds. These warnings are expressed as objects implementing the IInspectionWarningEvent
interface.
interface IInspectionWarningEvent {
timeRemaining: number;
}
Event properties
timeRemaining
is the time remaining on the inspectiong timer inmilliseconds
The total inspection time allowed by the WCA is 15 + 2
seconds. If the competitor hasn't started their solve by the
end of the initial 15 seconds, the solve is penalized by 2 seconds. If by the end of the total 17 seconds, the solve
still hasn't started, the solve is marked DNF.
(TODO: This looks clumsy, two fields that are mutually exclusive fields. Make them constants and just send the penalty type with the event)
enum PenaltyType {
PlusTwo = "PENALTY_PLUS_TWO";
DNF = "PENALTY_DNF";
};
interface IPenaltyEvent {
type: PenaltyType
}
Event properties
type
: what are you supposed to write here if you have the interface and enum definition above?!
interface ITickEvent {
phase: SolvePhase;
time: number
}
Event properties
phase
is the current phase of the solve.time
is the elapsed time since the laststatus
change. Basically, this is the either the current inspection time or the current solve time.
Note: If you have set options.noInspect
to true, all tick
events will have the phase SolvePhase.Solving
.
(TODO: the interface name and the event name don't match. Then again I want this to be as thin a wrapper around tiny-timer
as possible)
This event will be emitted as a result of any of the following:
- calling
timer.stop()
- calling
timer.abort()
- not calling either of the above methods within
options.timeLimit
ifoptions.timeLimit
wasn't set to0
during initialization.
interface ISolveEndEvent {
inspectionTime: number;
isDNF: boolean;
penalized: boolean;
solveTime: number;
}
Event properties
inspectionTime
: total inspection time for the solve inmilliseconds
.isDNF
:true
if a solve wasn't started before the total 17 seconds of inspection time or iftimer.abort()
was called.penalized
: will betrue
if this solve was penalized during the inspection phase.solveTime
: the actual solve time inmilliseconds
cubeTimer = new canCubeTimer({
interval: 100,
noInspect: false,
timeLimit: 10 * 60,
// event handlers
onInspectionWarning: (timeRemaining) => {},
onPenalty: (isDNF, timeRemaining) => {},
onTick: (status, time, timeRemaining) => {},
onDone: ({ inspectionTime, isDNF, penalized, solveTime }) => {},
})
Create a new timer instance. All options are optional. can-cube-timer
instances are EventEmitter
s.
Arguments
interval
: refresh interval inmilliseconds
.noInspect
: disable inspection. Calling thestart
method will directly start the timer in withstatus
set toSOLVING
.timeLimit
: the time limit for a solve inseconds
. WCA default is 10 minutes. Set to0
to disable
The remaining options onInspectionWarning
, onPenalty
, onStateChange
, onTick
and onDone
are just another way of attaching
event listeners to the various events.
Since a CanCubeTimer
is an EventEmitter
, so you can always add event listeners like:
const timer = new canCubeTimer();
timer.on('inspectionWarning', () => {});
timer.on('penalty', () => {});
timer.on('tick', () => {});
timer.on('done', () => {});
Start the timer. This is meant to be a convenience method that you can call on a timer instance. Depending on the current status of the timer, it will either start the inspection or the solve.
If you try to call start
on a timer that is already in the SolvePhase.Solving
status an error will be thrown.
(TODO: Error definition)
Disclaimer
This is a stateful method that either calls the startInspection
or the startSolve
method. It is meant to make it possible
to write code that is less verbose.
It also lets you write code that is (in my opinion) fragile. It is better to use the more explicit methods.
If you try to call this method more than twice during a solve, an error will be thrown. (TODO: Error defnition. Should be more specific than the previous one)
Starts the inspection.
An error will be thrown
- if you try to call it more than once during a solve
- if you try to call it on a timer that was created with
options.noInspect
set totrue
- if you try to call it on a timer that is in the
SolvePhase.Solving
phase
Starts the actual solve. If you try to call it more than once an error will be thrown.
Stops the solve. An EventTypes.Done
event will be emitted and the same event that is passed to registered event listeners
will be returned.
(TODO: Should there be a separate event for this?)
Aborts a solve. It stops the timer but the emitted ISolveEndEvent
event object will have the isDNF
field set to true