Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1881,6 +1881,14 @@
"lists"
],
"difficulty": 10
},
{
"slug": "split-second-stopwatch",
"name": "Split-Second Stopwatch",
"uuid": "9510c0ae-9977-4260-8991-0e8e849094b0",
"practices": [],
"prerequisites": [],
"difficulty": 1
}
],
"foregone": [
Expand Down
22 changes: 22 additions & 0 deletions exercises/practice/split-second-stopwatch/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Instructions

Your task is to build a stopwatch to keep precise track of lap times.

The stopwatch uses four commands (start, stop, lap, and reset) to keep track of:

1. The current lap's tracked time
2. Previously recorded lap times

What commands can be used depends on which state the stopwatch is in:

1. Ready: initial state
2. Running: tracking time
3. Stopped: not tracking time

| Command | Begin state | End state | Effect |
| ------- | ----------- | --------- | -------------------------------------------------------- |
| Start | Ready | Running | Start tracking time |
| Start | Stopped | Running | Resume tracking time |
| Stop | Running | Stopped | Stop tracking time |
| Lap | Running | Running | Add current lap to previous laps, then reset current lap |
| Reset | Stopped | Ready | Reset current lap and clear previous laps |
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Introduction

You've always run for the thrill of it — no schedules, no timers, just the sound of your feet on the pavement.
But now that you've joined a competitive running crew, things are getting serious.
Training sessions are timed to the second, and every split second counts.
To keep pace, you've picked up the _Split-Second Stopwatch_ — a sleek, high-tech gadget that's about to become your new best friend.
19 changes: 19 additions & 0 deletions exercises/practice/split-second-stopwatch/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"EmmanuelBerkowicz"
],
"files": {
"solution": [
"src/main/java/SplitSecondStopwatch.java"
],
"test": [
"src/test/java/SplitSecondStopwatchTest.java"
],
"example": [
".meta/src/reference/java/SplitSecondStopwatch.java"
]
},
"blurb": "Keep track of time through a digital stopwatch.",
"source": "Erik Schierboom",
"source_url": "https://github.com/exercism/problem-specifications/pull/2547"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import java.util.ArrayList;
import java.util.List;

public class SplitSecondStopwatch {

/**
* A split-second stopwatch that tracks elapsed time with lap functionality.
* Supports start, stop, reset, and lap operations with precise time tracking.
* Times are formatted in HH:MM:SS format with two-digit precision.
*
* @see <a href="https://github.com/exercism/problem-specifications/tree/main/exercises/split-second-stopwatch">
* Problem Specifications
* </a>
*/

private enum State { READY, RUNNING, STOPPED }

private State state;
private long totalCompletedLaps; // Total time from completed laps
private long currentLapStart; // When current lap started
private long accumulated; // Accumulated time for current lap when stopped
private List<String> previousLaps;
private long mockTime;

public SplitSecondStopwatch() {
this.state = State.READY;
this.totalCompletedLaps = 0;
this.currentLapStart = 0;
this.accumulated = 0;
this.previousLaps = new ArrayList<>();
this.mockTime = 0;
}

public void start() {
if (state == State.RUNNING) {
throw new IllegalStateException("cannot start an already running stopwatch");
}

currentLapStart = mockTime;
state = State.RUNNING;
}

public void stop() {
if (state != State.RUNNING) {
throw new IllegalStateException("cannot stop a stopwatch that is not running");
}

accumulated += mockTime - currentLapStart;
state = State.STOPPED;
}

public void reset() {
if (state != State.STOPPED) {
throw new IllegalStateException("cannot reset a stopwatch that is not stopped");
}

state = State.READY;
totalCompletedLaps = 0;
currentLapStart = 0;
accumulated = 0;
previousLaps.clear();
}

public void lap() {
if (state != State.RUNNING) {
throw new IllegalStateException("cannot lap a stopwatch that is not running");
}

long currentLapTime = getCurrentLapTime();
totalCompletedLaps += currentLapTime;
previousLaps.add(formatTime(currentLapTime));

// Reset current lap and restart
accumulated = 0;
currentLapStart = mockTime;
}

public String state() {
return state.name().toLowerCase();
}

public String currentLap() {
return formatTime(getCurrentLapTime());
}

public String total() {
return formatTime(totalCompletedLaps + getCurrentLapTime());
}

public List<String> previousLaps() {
return new ArrayList<>(previousLaps);
}

public void advanceTime(String timeString) {
String[] parts = timeString.split(":");
long hours = Long.parseLong(parts[0]);
long minutes = Long.parseLong(parts[1]);
long seconds = Long.parseLong(parts[2]);

long milliseconds = (hours * 3600 + minutes * 60 + seconds) * 1000;
mockTime += milliseconds;
}

private long getCurrentLapTime() {
switch (state) {
case READY:
return 0;
case RUNNING:
return accumulated + (mockTime - currentLapStart);
case STOPPED:
return accumulated;
default:
throw new IllegalStateException("Invalid state");
}
}

private String formatTime(long milliseconds) {
long totalSeconds = milliseconds / 1000;
long hours = totalSeconds / 3600;
long minutes = (totalSeconds % 3600) / 60;
long seconds = totalSeconds % 60;

return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
}
97 changes: 97 additions & 0 deletions exercises/practice/split-second-stopwatch/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[ddb238ea-99d4-4eaa-a81d-3c917a525a23]
description = "new stopwatch starts in ready state"

[b19635d4-08ad-4ac3-b87f-aca10e844071]
description = "new stopwatch's current lap has no elapsed time"

[492eb532-268d-43ea-8a19-2a032067d335]
description = "new stopwatch's total has no elapsed time"

[8a892c1e-9ef7-4690-894e-e155a1fe4484]
description = "new stopwatch does not have previous laps"

[5b2705b6-a584-4042-ba3a-4ab8d0ab0281]
description = "start from ready state changes state to running"

[748235ce-1109-440b-9898-0a431ea179b6]
description = "start does not change previous laps"

[491487b1-593d-423e-a075-aa78d449ff1f]
description = "start initiates time tracking for current lap"

[a0a7ba2c-8db6-412c-b1b6-cb890e9b72ed]
description = "start initiates time tracking for total"

[7f558a17-ef6d-4a5b-803a-f313af7c41d3]
description = "start cannot be called from running state"

[32466eef-b2be-4d60-a927-e24fce52dab9]
description = "stop from running state changes state to stopped"

[621eac4c-8f43-4d99-919c-4cad776d93df]
description = "stop pauses time tracking for current lap"

[465bcc82-7643-41f2-97ff-5e817cef8db4]
description = "stop pauses time tracking for total"

[b1ba7454-d627-41ee-a078-891b2ed266fc]
description = "stop cannot be called from ready state"

[5c041078-0898-44dc-9d5b-8ebb5352626c]
description = "stop cannot be called from stopped state"

[3f32171d-8fbf-46b6-bc2b-0810e1ec53b7]
description = "start from stopped state changes state to running"

[626997cb-78d5-4fe8-b501-29fdef804799]
description = "start from stopped state resumes time tracking for current lap"

[58487c53-ab26-471c-a171-807ef6363319]
description = "start from stopped state resumes time tracking for total"

[091966e3-ed25-4397-908b-8bb0330118f8]
description = "lap adds current lap to previous laps"

[1aa4c5ee-a7d5-4d59-9679-419deef3c88f]
description = "lap resets current lap and resumes time tracking"

[4b46b92e-1b3f-46f6-97d2-0082caf56e80]
description = "lap continues time tracking for total"

[ea75d36e-63eb-4f34-97ce-8c70e620bdba]
description = "lap cannot be called from ready state"

[63731154-a23a-412d-a13f-c562f208eb1e]
description = "lap cannot be called from stopped state"

[e585ee15-3b3f-4785-976b-dd96e7cc978b]
description = "stop does not change previous laps"

[fc3645e2-86cf-4d11-97c6-489f031103f6]
description = "reset from stopped state changes state to ready"

[20fbfbf7-68ad-4310-975a-f5f132886c4e]
description = "reset resets current lap"

[00a8f7bb-dd5c-43e5-8705-3ef124007662]
description = "reset clears previous laps"

[76cea936-6214-4e95-b6d1-4d4edcf90499]
description = "reset cannot be called from ready state"

[ba4d8e69-f200-4721-b59e-90d8cf615153]
description = "reset cannot be called from running state"

[0b01751a-cb57-493f-bb86-409de6e84306]
description = "supports very long laps"
25 changes: 25 additions & 0 deletions exercises/practice/split-second-stopwatch/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
id "java"
}

repositories {
mavenCentral()
}

dependencies {
testImplementation platform("org.junit:junit-bom:5.10.0")
testImplementation "org.junit.jupiter:junit-jupiter"
testImplementation "org.assertj:assertj-core:3.25.1"

testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}

test {
useJUnitPlatform()

testLogging {
exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
public class SplitSecondStopwatch {
public void start() {
throw new UnsupportedOperationException("Please implement the SplitSecondStopwatch.start method.");
}

public void stop() {
throw new UnsupportedOperationException("Please implement the SplitSecondStopwatch.stop method.");
}

public void reset() {
throw new UnsupportedOperationException("Please implement the SplitSecondStopwatch.reset method.");
}

public void lap() {
throw new UnsupportedOperationException("Please implement the SplitSecondStopwatch.lap method.");
}

public String state() {
throw new UnsupportedOperationException("Please implement the SplitSecondStopwatch.state method.");
}

public String currentLap() {
throw new UnsupportedOperationException("Please implement the SplitSecondStopwatch.currentLap method.");
}

public String total() {
throw new UnsupportedOperationException("Please implement the SplitSecondStopwatch.total method.");
}

public java.util.List<String> previousLaps() {
throw new UnsupportedOperationException("Please implement the SplitSecondStopwatch.previousLaps method.");
}

public void advanceTime(String timeString) {
throw new UnsupportedOperationException("Please implement the SplitSecondStopwatch.advanceTime method.");
}
}
Loading
Loading