Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Implement a simple digital Stopwatch, which will display the time elapsed in seconds and minutes, as described below.

Display

Time elapsed, should be displayed in the MM:SS format, by replacing the previously displayed time string "in-place" (clearing the whole or a part of the screen is also allowed).

The stopwatch must be updated at least every second.

Examples:

0 minutes, 0 seconds

00:00

0 minutes, 33 seconds

00:33

1 minute, 50 seconds

01:50

Initially, you can start with '00:00' or with any other value in range [00:00-59:59].

Once your Stopwatch reaches 59:59, it should reset to 00:00 and continue afresh.

You can use a different base (instead of decimal) or even a different numeral system if you wish, as long as you follow the general pattern.

For example 13:03 can be displayed as:

Decimal

13:03

Hexadecimal

0D:03

Base64

N:D

Quater-imaginary base

10101:3

Roman Numerals

XIII:III

Beware that if you use a non-decimal numeral system/base, it must be encoded using printable ASCII (or Unicode) characters, e.g. using two binary (unprintable) bytes for minutes and seconds is not allowed.

You must also left-pad your output with zeroes as appropriate, if your numerical system allows for that.

Replacing the separator character : with any other printable character (including digits) is also acceptable.

Controls

The stopwatch should start paused, and stay in this state, until user explicitly starts it, by pressing the 'control' key (see below).

If, while stopwatch is counting, user presses the 'control' key again, the stopwatch should pause (keeping the current time), until the 'control' key is pressed a one more time.

The 'control' key can be a single keystroke, e.g. s, or any combination of keys, e.g. Ctrl+Shift+X, but it must be 'atomic', pressing multiple keys in sequence, e.g. s then Enter, is not allowed.

The same 'control' key (or combination) must be used to pause and resume the stopwatch.

You must use a specific 'control' key, i.e. 'any key' is not allowed.

Alternatively, you can use a single or double mouse-click, instead of a keypress for 'control'.


Rules

  • This is , the shortest answer in bytes wins;
  • Standard code-golf loopholes apply;
  • Your program must (theoretically) be capable of running forever.
share|improve this question
    
Can the 'control' key be enter? – Loovjo 15 hours ago
    
@Loovjo Yes, any single key or combination of keys will do, including Enter (as long it can be paused and then resumed using the same key). – zeppelin 15 hours ago
    
related – Jonathan Allan 12 hours ago
    
Do we need sub-second granularity? I.e. if the user pauses approximately 7000 milliseconds after 00:05 is printed, and then at some point resumes again, must the 00:06 appear 3000 milliseconds after the resume key was pressed, or is it okay to print it a full second after the resume key was pressed? – smls 11 hours ago
    
@smls It is ok to wait a full second, after the resume. – zeppelin 11 hours ago

Python 2, 167 131 bytes

-36 bytes mostly* from using Maltysen's idea of catching ctrl-c using an exception - go give credit!
-4 bytes thanks to DLosc (init n and b to 0 rather than f())
-1 byte thanks to FlipTack (use p^=1 rather than p=1-p)

import time
f=time.time
n=b=p=0
while 1:
 try:n=[n,f()][p];t=n-b;print'\r%0.2d:%0.2d'%(t/60%60,t%60),
 except:b=[b-n+f(),b][p];p^=1

Works the same as my original, below, but with the control key sequence of ctrl+c.
Might work on non-Windows OS (?).

* also collapsed if statement to a list lookup in order to get the try as a one-liner.

My original:

import time,msvcrt as m
f=time.time
n=b=p=0
while 1:
 if m.kbhit()and m.getch()==b'p':b=[b-n+f(),b][p];p^=1
 if p:n=f()
 t=n-b;print'\r%0.2d:%0.2d'%(t/60%60,t%60),

(Tested on Python 2.7.8 on Windows 7, 64bit - the code is Windows specific)

The control key is 'p'.

n and b are initialised to the same value at start-up, giving an "offset" of 0; p is initialised to 0, indicating a paused state.

Whenever the control key is pressed the value of p is switched. When switching from a paused state to an active state b is updated to a new value keeping any current offset from the previous active state(s) with b-n.

During an active state n is repeatedly updated to the current time by calling time.time().

The difference between n and b, t, is then the total number of seconds (including a fractional part) elapsed during active state(s).

The minutes elapsed are then t/60 and each of the minutes and seconds are displayed mod 60 with (t/60%60,t%60). Leading zeros are prepended for each using string formatting of the integer part with '...%0.2d...'. Printing a tuple (the trailing ,) where the first item has a leading carriage return (the \r) causes the previously printed text to be overwritten.

share|improve this answer
    
Ah yes, good catch, I originally did have ^= but switched at some point during formulation. – Jonathan Allan 13 hours ago
    
@DLosc indeed, thanks... – Jonathan Allan 12 hours ago

SmileBASIC, 86 77 71 bytes

@L
N=N!=DIALOG(FORMAT$("%02D:%02D",F/60MOD 60,F MOD 60),,,N)F=F+1GOTO@L

DIALOG displays a textbox on the touch screen. N is the number of seconds the text box will stay on screen before it disappears. If N is 0, it stays until the user presses the button on the touch screen.

DIALOG Returns 1 if the user pressed the button, and 0 if it closed automatically. So when the user pushes the pause button, it returns 1, and the display time is set to 0, pausing the stopwatch. After the user presses the button again, we set the display time back to 1, resuming the timer. Basically, every time DIALOG returns 1, the display time is switched between 1 and 0 using !=, which is eqivilant to a logical XOR as long as both inputs are 1 or 0.

share|improve this answer
    
The explanation helps, and I am beginning to be impressed. Another question, though: What happens when the user presses the button several times in less than a second? (Also: it might help readers if you explain that = is assignment but != is comparison. It took me a while to figure that out.) – DLosc 13 hours ago

Python - 160 159 143 bytes

Thanks to @JonathanAllan for saving me 18 bytes!

Only uses builtin libraries, so the control key is ctrl-c, catching it with an except keyboardInterrupt.

import time
Z=0
print'00:00'
while 1:exec"try:\n while 1:\n  %s\nexcept:1\n"*2%(1,"print'\033c%02d:%02d'%divmod(Z%3600,60);Z+=1;time.sleep(1)")
share|improve this answer
    
Oh nice. I think maybe it could shorter with just except:? I have a working version of mine doing it... – Jonathan Allan 12 hours ago
    
@JonathanAllan oh cool, didn't know you could do that. – Maltysen 12 hours ago

C# 221 Bytes

using static System.Console;
using static System.DateTime;
class P
{
    static void Main()
    {
        var l = Now;
        var d = l-l;
        for( var r = 1<0;;Write($"\r{d:mm\\:ss}"))
        {
            if (KeyAvailable && ReadKey(1<2).KeyChar == 's')
            {
                l = Now;
                r = !r;
            }
            if (r)
                d -= l - (l = Now);
        }

    }
}

Golfed

using static System.Console;using static System.DateTime;class P{static void Main(){var l=Now;var d=l-l;for(var r=1<0;;Write($"\r{d:mm\\:ss}")){(KeyAvailable&&ReadKey(1<2).KeyChar=='s'){l=Now;r=!r;}if (r)d-=l-(l=Now);}}}

Using the s key to start/stop. Whole program works by remembering the TimeDelta using DateTime.Now

Most C#-Magic here comes from the C# 7.0 feature using static.

share|improve this answer

QBasic, 213 211 bytes

Control key is tab. Leaving this running may cause laptop fires. You have been warned.

DO
WHILE k$<>CHR$(9)
k$=INKEY$
LOCATE 1
?CHR$(48+m\10);CHR$(48+(m MOD 10));":";CHR$(48+(d MOD 60)\10);CHR$(48+(d MOD 10))
IF r THEN
n=TIMER
d=v+n-b+86400
m=d\60MOD 60
END IF
WEND
k$=""
v=v+n-b
r=1-r
b=TIMER
LOOP

Here it is in action, pausing at 10, 15, and 20 seconds:

Stopwatch running

Ungolfed and commented

' Outer loop runs forever
DO
  ' The WHILE-WEND loop runs until tab is pressed
  WHILE key$ <> CHR$(9)
    key$ = INKEY$
    ' Output the stopwatch value at top left of screen
    LOCATE 1
    ' Unfortunately, QBasic's PRINT USING doesn't have a format for printing
    ' with leading zeros, so we have to do it manually by printing the
    ' 10s digit and the 1s digit
    PRINT CHR$(48 + minute \ 10); CHR$(48 + (minute MOD 10));
    PRINT ":";
    PRINT CHR$(48 + second \ 10); CHR$(48 + (second MOD 10))
    ' Update the current time if the running flag is set
    IF running THEN now = TIMER
    ' Take the difference between now and the last time we started the
    ' stopwatch, plus the amount of saved time from previous runs,
    ' plus 86400 to account for the possibility of running over midnight
    ' (since TIMER is the number of seconds since midnight, and QBasic's
    ' MOD doesn't handle negative values like we would need it to)
    diff = saved + now - lastStarted + 86400
    second = diff MOD 60
    minute = diff \ 60 MOD 60
  WEND
  ' If we're outside the WHILE loop, the user pressed tab
  key$ = ""
  ' Add the previous run's time to the saved amount
  saved = saved + now - lastStarted
  ' Toggle running between 0 and 1
  running = 1 - running
  ' If we're starting, we want to put the current time in lastStarted;
  ' if we're stopping, it doesn't matter
  lastStarted = TIMER
LOOP

Note that values of TIMER are floating-point. This doesn't affect the output, since MOD and \ truncate to integers. But it does add accuracy to the amount of saved time: if you pause the timer right before a tick, you'll see when you start it up again that the number changes in less than a second.

share|improve this answer

Batch, 132 bytes

set/ar=0,m=s=100
:l
cls
@choice/t 1 /d y /m %m:~1%:%s:~1% /n
set/as+=r,m+=c=s/160,s-=c*60,m-=m/160*60,r^^=%errorlevel%-1
goto l

Pressing n will (un)pause the timer. The output flicker can be reduced at a cost of three (or four) bytes.

share|improve this answer

C 309 bytes

#include<termios.h>f(){m=0,s=0;struct termios i;tcgetattr(0,&i);i.c_lflag &=~ICANON;i.c_cc[VMIN]=0;i.c_cc[VTIME]=0;tcsetattr(0,TCSANOW,&info);A: while(getchar()^'\n'){if(s++==59){if(m++==59)m=0;s=0;}printf("\r%02d:%02d",m,s);sleep(1);system("clear");if(getchar()=='\n'){break;}}while(getchar()^'\n'){}goto A;}

Ungolfed version:

#include<termios.h> //Included to use the termios structure
void f()
{
   int m=0,s=0;
   struct termios i;
   tcgetattr(0, &i);     // get current terminal attirbutes; 0 is the file descriptor for stdin 
   i.c_lflag &  = ~ICANON;  // disable canonical mode 
   i.c_cc[VMIN] = 0;    // don't wait for any keystrokes before reading from stdin 
   i.c_cc[VTIME]= 0;    // no timeout
   tcsetattr(0, TCSANOW, &i); // set immediately 

   A: while(getchar()^'\n')
      {           
       if(s++==59)
       {
         if(m++==59)
           m=0;

         s=0;
       }
       printf("\r%02d:%02d",m,s);
       sleep(1);  
       system("clear");

        if(getchar()=='\n')
        {
          break;
        }
      }

       while(getchar()^'\n')
       {}
       goto A ;

}

Usage: Press Enter to Pause and Resume the Stopwatch.

Explanation:

  • Put the terminal in non-canoncial mode in order to capture the keystrokes immediately.
  • Wait for Enter keystroke, break the first while loop and wait until next Enter comes.
  • Upon next Enter keystroke, goto first while loop and resume counting.

Now, i know goto is a bad coding practice in C, but i could not figure out another way.

share|improve this answer

HTML + JavaScript (ES6), 191 192 187 183 bytes

<b id=a>00:00</b><a onclick='b=b?clearInterval(b):setInterval("a.innerHTML=`${(d=(((c=a.innerHTML.split`:`)[1]>58)+c[0])%60)>9?d:`0`+d}:${(e=++c[1]%60)>9?e:`0`+e}",1e3)'onload='b=0'>S

Explanation

Click the S text to start or pause the stopwatch. As such, a single mouseclick is the control key. The separator between the two values is a colon.

Whenever the user clicks the link, the value of b is checked. It is initialised to 0 which evaluates to false, so a string of code is evaluated every 1000 milliseconds. This sets the variable to the id of the interval, so it can be stopped later. If b contains a number, it evaluates to true, so the interval is stopped. This in turn returns the value undefined.

The string of code changes the html of the element with id a (the stopwatch). First the minutes are parsed by taking the previous stopwatch value, splitting it by the colon, and getting the minutes value, which is increased by 0 if the value of the seconds is not 59 (greater than 58), and 1 otherwise, modulo 60. Then this value is padded. Then comes the colon, and lastly, the seconds. The code simply gets the old value, increases it by 1, takes modulo 60 and optionally pads it.

share|improve this answer
    
This doesn't seem to work at all. I just get ReferenceError: d is not defined – Alexis Tyler 2 hours ago
    
You can probably also save a few bytes by removing the href=# since it's not actually needed since you're using onclick. – Alexis Tyler 2 hours ago
    
I just fixed that. I also removed the href, because you were right. Thanks! – Luke 1 hour ago

bash + Unix utilities, 88 or 93 bytes

88-byte version:

trap d=\$[!d] 2;for((n=0;;)){(($d))&&dc<<<DP60dod*d$n\r%+n|colrm 1 4&&: $[n++];sleep 1;}

93-byte version:

trap d=\$[!d] 2;for((n=0;;)){(($d))&&dc<<<DP60dod*$n+n|colrm 1 4&&: n=$[(n+1)%3600];sleep 1;}

Ctrl-C is the resume/pause character. A space is the delimiter between minutes and seconds.

The difference between the two versions is that the 88-byte program will work for 2^63 seconds (at which point, bash will give me an integer overflow).

The 93-byte version will truly work forever.

The original problem included the requirement: "Your program must (theoretically) be capable of running forever."

If running for 2^63 seconds is sufficient to meet that requirement, then the 88-byte solution works. That duration is more than 20 times the age of the universe!

If the program needs to be able to run for longer than that, I'll have to go with the 93-byte solution.


I should probably point out that this solution, as well as at least some of the others posted, will very slowly fall behind the true elapsed time. This slippage is because the program is sleeping for one second between each execution of the body of the loop, but the body of the loop does take some tiny amount of time to execute. This will be inconsequential in practice.

share|improve this answer
    
It looks like this will not display an initial value on the screen, until you "unpause" it. – zeppelin 37 mins ago
    
"The stopwatch should start paused, and stay in this state, until user explicitly starts it, by pressing the 'control' key (see below)." Is there a spec I missed? – Mitchell Spector 9 mins ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.