9

Bullet chess gives each player 1 minute to make all their moves - if that minute passes and they haven't checkmated their opponent, they lose (or if insufficient material, a draw).

Since chess has 2 players, the longest a bullet chess game can go for is approximately 2 minutes (or slightly under because it's not possible for both clocks to count down to exactly zero at the same time).

But many bullet games are much shorter than 2 minutes, for example in games where a checkmate or resignation happens very early in the game (some last only a few seconds).

So what is the average duration of a game of bullet chess?

DialFrost
  • 1,133
  • 1
  • 4
  • 26
stevec
  • 2,077
  • 11
  • 27
  • 6
    I think combing the average of the last 10000 or so bullet games on lichess would give a pretty good estimate. – Rewan Demontay Feb 08 '22 at 04:31
  • 4
    @RewanDemontay thanks for the great suggestion. I checked out the lichess API and couldn't find an easy way to get the last 10k bullet games. Sure enough, I joined the lichess discord (api-bots-boards channel) and asked, and within 5 seconds flat Thibault himself replied, who said it's not possible via API, but you can download the entire month's database). Unfortunately it's about 30GB - too big for me to download right now, but if anyone's got the bandwidth, here's the link. Perhaps they could grab the last 10k bullet games and pastebin them for us all to use. https://database.lichess.org/ – stevec Feb 08 '22 at 12:18

3 Answers3

13

Per the suggestion by Rewan in the comments, I pulled the January 2022 log of games from lichess and looked at all the 1+0 games that finished with "Normal" or "Time forfeit" results with both players making at least one move each.

The code I used is included below.

Notes:

  • Elapsed time is calculated as the sum of the starting clocks for each player minus the sum of the ending clocks for each player. Starting clocks (rather than a constant 120s) are used to handle the case when a player berserks.

  • I exclude cases where players gave their opponents more time (since this makes calculating elapsed time impossible from the PGN data given). At least, I try to; there are some cases that will be undetectable, such as giving time and the opponent using up all of that time prior to making their next more.

  • I do not use wall clock game creation time and game end time so as to avoid having the calculation artificially inflated with the extra non-game time introduced by the delay between game creation and game start and lag/ping latency.

Number of games: 27,398,824
Average time: 93.51 seconds
Median time: 104 seconds

Using just the games in which neither player berserks:

Number of games: 27,130,482
Average time: 93.77 seconds
Median time: 105 seconds

Histogram of durations in that second set (no berserks):

Histogram of values with duration of games as the x-axis, showing a slow increase from 0 seconds to 80 seconds transitioning to a rapid growth from 80 seconds to 120 seconds

Note the blip at t=60s. This duration is over-represented due to the games that have exactly two moves with white timing out. These stem, I believe, from challenges in which the challenged player does not immediately respond, and the challenger (playing white) doesn't notice when the challenge is ultimately accepted.

Also note that the duration of the game is at least somewhat dependent on the strengths of the players. Here's a graph of the durations for games in which both players were about the same strength (Elo within 100 of each other) plotted against the players' average rating:

enter image description here

I'm guessing the sudden dip at the end is an artifact stemming from the lower game count there, although perhaps to reach that level, premoves really become much more necessary.

python3 code:

import glob
import os
import re
import statistics

def seconds(time_str):
    """convert h:mm:ss to total seconds"""
    h, m, s = time_str.split(':')
    return int(h) * 3600 + int(m) * 60 + int(s)

# function to verify each player's clock is monotone
# decreasing (if a player is given time by their 
# opponent, then the calculation of total game time
# is not possible given only the PGN data supplied)
def non_increasing(l):
    return all(x>=y for x, y in zip(l, l[1:]))

clock = re.compile(r'(?:\[%clk )(\d:\d\d:\d\d)')

ignore = 0
bullet = 0
time = 0
gameid = ""
alltimes = []
sanetimes = []

statfile = open('stats.txt','w')
statfile.write("%8s %3s %3s %5s %5s\n"
  % ("GameID", "Dur", "nPly", "Wclk", "Bclk"))

for filename in glob.glob('*.pgn'):
  print(filename)
  with open(os.path.join(os.getcwd(), filename), 'r') as f:
    line = f.readline()

    while(line):

      # "Site" is the second line of the each game, so it
      # is a good place to serve up the reset
      if(line.find('[Site ')>=0):
        ignore = 0
        timeout = 0
        bullet = 0
        s = line.split('"')
        s = s[1].split('/')
        gameid = s[-1]

      if(line.find('[Variant')>=0):
        ignore = 1

      if(line.find('[TimeControl')>=0):
        s = line.split('"')
        if(s[1] == '60+0'):
          bullet = 1

      # ignore "Abandoned", "Unterminated", "Rules infraction"
      # and any other nonstandard endings.
      if(line.find('[Termination')>=0):
        s = line.split('"')
        if(s[1] == "Time forfeit"):
          timeout = 1
        elif(s[1] != "Normal"):
          ignore = 1

      if(line[0] == '1'):
        if((bullet == 1) and (ignore == 0)):
          times = clock.findall(line)
          if(len(times) >= 2):
            secs = [ seconds(item) for item in times ]
            p1 = secs[::2]
            p2 = secs[1::2]

            # record the first clock value for each player.
            # it should be 60, but if the player berserked
            # then it will be 30. Whichever, we use this
            # value to determine the elapsed time for that
            # player's clock at the end.
            s1 = p1[0]
            s2 = p2[0]

            if(s1<=60 and s2<=60 and non_increasing(p1)
                and non_increasing(p2)):
              t0 = secs[-1]
              if(timeout == 0):
                t1 = secs[-2]
                dur = s1+s2 - (t0 + t1)
              else:
                dur = s1+s2 - t0

              if(dur >= 0 and dur <=120):
                statfile.write("%8s %3d %3d %5d %5d\n"
                  % (gameid, dur, len(secs), s1, s2))

                alltimes.append(dur)
                if(s1==60 and s2==60):
                  sanetimes.append(dur)
        
      line = f.readline()

f.close()
statfile.close()

print("Number of games: ", len(alltimes))
print("Average time: %s seconds"
  % ( statistics.mean(alltimes)))
print("Median time: %s seconds"
  % ( statistics.median(alltimes)))

print("Number of non-berserked games: ", len(sanetimes))
print("Average time: %s seconds"
  % ( statistics.mean(sanetimes)))
print("Median time: %s seconds"
  % ( statistics.median(sanetimes)))
L. Scott Johnson
  • 1,368
  • 1
  • 7
  • 14
  • 1
    This is great. Just wanted to check if it includes resignations? – stevec Feb 10 '22 at 20:25
  • 4
    Yes: mates, stalemates, draws, resignations, and loss on time are all counted. I can rerun it with a different filter if you like. – L. Scott Johnson Feb 10 '22 at 20:38
  • 2
    that’s perfect just wanted to confirm. – stevec Feb 10 '22 at 20:41
  • 1
    No sorting: I just look for time control of 60+0 (it's given in seconds) and exclude certain cases like Rules Violations then look at the last two %clk values. I note that if anyone "gives time", then this clk value will throw off the calculations (e.g., in some cases, a player ends with more than 60 seconds left on their clock, which the calculation will see as the game taking negative time: https://lichess.org/HySUH6IK ). I'll see about adding code to check that the clocks are at least non-increasing. – L. Scott Johnson Feb 11 '22 at 13:38
  • 1
    Adding for completeness: someone mentioned in another forum "*A berzeked game still has [TimeControl "60+0"] , so they are probably included ...You would need to look at the move time in the PGN's to tell if a player zerked. (Now, I don't think this would really change the numbers that much. At most a few seconds? Guessing here thru, but from all rated games, not that many are zerked)*" – stevec Feb 11 '22 at 14:07
  • 1
    @stevec. Good point. Just the first clock for each player should tell you if a player berserks. The question is then whether to count that game in the stats or not. I'll run it both ways. – L. Scott Johnson Feb 11 '22 at 15:16
  • Hi @L.ScottJohnson I wonder if you might post the code you used to analyze this somewhere? I got slightly different results and was also wondering how you were able to go through such a massive file so quickly. I left my computer running overnight trying to analyze the games, but it had not finished by morning. Also, I could not figure out an efficient way to handle situations where someone has given his opponent extra time. – Dargscisyhp Feb 13 '22 at 17:02
  • Sure. I'm re-running the code now to handle one more type of outlier: the monotonic case where "add time" was used prior to the first move. e.g.: https://lichess.org/VDLllWBA . I'll post "sane" stats (assuming no other outliers are revealed) and the code when that run is done (takes about 2.5 hours). – L. Scott Johnson Feb 14 '22 at 13:09
  • 1
    @L.ScottJohnson I'm impressed that your computer is able to process the entire database of games in 2.5 hours. I was able to extract out the first million bullet games, but that alone took a few hours. I also find the huge number of games that go to the wire interesting. I wonder why I did not find the same. The histograms created from my data can be found at this [Math stackexchange question](https://math.stackexchange.com/questions/4381681/what-sort-of-distribution-should-you-expect-for-the-total-time-taken-in-bullet-c) – Dargscisyhp Feb 15 '22 at 19:38
  • A couple of differences in our methodologies: I did not exclude "nonstandard" endings, which if I go through a second pass I will. I limited to games with an event title of "Rated Bullet game" so I did not have to exclude berserkers. I did not check to see if every time was nonincreasing, so I will have included some instances where time was given when I should not have. Overall the statistics were close -- mean total time was 92.3 and median was 104. However, none of this explains why you have a blip at t=120 and I don't (at least not that I can see) – Dargscisyhp Feb 15 '22 at 19:44
  • If you limited to "Rated Bullet game", that itself may be why you saw no (noticeable) blip at 60s (meaning unrated games are more prone to such, IMO). If you're interested, my hardware is a 2017 iMac, 3.8 GHz Quad-Core Intel Core i5, 64GB RAM, reading from a PCI SSD. – L. Scott Johnson Feb 15 '22 at 20:02
  • @Dargscisyhp For the games that time out, are you taking the timed-out player's final clock to be zero or are you using their last %clk value? – L. Scott Johnson Feb 15 '22 at 20:09
  • @L.ScottJohnson I'm using their last %clk value. Perhaps there's the discrepancy? I don't think the database includes unrated games but I may be mistkaen. – Dargscisyhp Feb 15 '22 at 20:13
  • @Dargscisyhp That may explain why you're not getting blips at 60s and 120s. When the ending condition is timeout, the timed-out player's final clock is 0 but this is not accurately reflected in the PGN's move list. A 2-ply game that takes 60s you will see as taking only 0s. – L. Scott Johnson Feb 15 '22 at 20:30
  • 1
    @L.ScottJohnson You're right, that was it. I can recreate the blips. 93.7 mean, 105 median (over 100,000 games). – Dargscisyhp Feb 15 '22 at 21:17
  • Why the spike at 120s? Do so many games really come down to less than a second? I guess it might be from the ability to premove and use no time. – Edward Feb 17 '22 at 02:20
  • @Edward yes. When you and your opponent are low on time, you tend to enter a time scramble where you both just push your pieces furiously around in the hope that your opponent flags – L. Scott Johnson Feb 17 '22 at 11:32
  • @L.ScottJohnson Yes, I know this. I was a bit surprised at the large difference between flags at <1s vs flags at 1-2s – Edward Mar 05 '22 at 15:18
4

Collected data from the top 100 bullet players in Lichess using the berserk module from lichess api.

The function client.games.export_by_player() can give info about the games played by a player. This includes createdAt and lastMoveAt timestamps values from which we can calculate the duration of the game.

Be noted that extracting game time duration through game clk info may not be appropriate because most of these players are pre-moving. Even if their clock is at 1s some players can still make some moves with their clock still at 1s.

I. Bullet games at TC 60+0

Some figures

Bullet games from top 100 bullet Lichess players, each player has around 200 games at tc 60+0 less duplicates.

num data: 18125
min   (s): 21
max   (s): 200
mean  (s): 103
stdev (s): 32

Overall distribution

There are games that are more than 120s due to pre-moves.

enter image description here

Resigned games

It peaks close to 100s.

enter image description here

Checkmates

enter image description here

Time forfeit

enter image description here

Draws

enter image description here

Stalemate

enter image description here

Overall result by game termination

enter image description here

II. Bullet games at TC 30+0

Bullet games from top 100 bullet Lichess players at TC 30+0

num data: 6963
min   (s): 21
max   (s): 120
mean  (s): 63
stdev (s): 17

enter image description here

ferdy
  • 3,795
  • 5
  • 18
  • 1
    How do premoves generate more time on the clock? – L. Scott Johnson Feb 16 '22 at 20:53
  • 1
    I wonder if that has to do with the way the server accounts for latency. I've wondered the same thing while playing on lichess -- games that are both down to where each player has 1 to 2 seconds left seem to go a lot longer than the 2-4 seconds indicated by the clock. – Dargscisyhp Feb 16 '22 at 21:00
  • 1
    @L.ScottJohnson [Here's an example](https://www.youtube.com/watch?v=G2AMN9tHSB0&t=2324s) where I believe that happens. This is an 8s/0 game on both sides, and starts at the 38:44 mark. At the end of the game the players have a combined 3.9 seconds left, which is at the 39:07 mark. That means 12.1 seconds of in game time elapsed, but 23 seconds of real time! – Dargscisyhp Feb 16 '22 at 21:06
  • @Dargscisyhp Sure. But the question is formulated around clock time, not real time. – L. Scott Johnson Feb 16 '22 at 22:16
  • 4
    @L.ScottJohnson Probably the question is formulated under the assumption that clock time and real time are one and the same. Either way, both answers are interesting. – Dargscisyhp Feb 16 '22 at 22:20
  • 1
    Another factor, perhaps a bigger factor, in the discrepancy is that createdAt is not the time the game started. The added time from those gaps (between game creation and game start) likely dwarfs the accumulated from pre-moves latency. As an extreme example, the 1+0 game I tested with, which ended after 3 seconds, shows 40 minutes and 20 seconds elapsed from creation to last move. – L. Scott Johnson Feb 19 '22 at 16:12
  • 1+0, 40m+20s, that is weird, could you post the game id? – ferdy Feb 19 '22 at 19:30
  • It's extreme length for a gap, but the gap existing is common. It's the difference between the time the game is created and the time the game begins. Reference test game: https://lichess.org/dotb5hRF – L. Scott Johnson Feb 19 '22 at 21:10
  • That game is casual. Can you find a rated game having a weird elapse time? In my datasets I use rated games. – ferdy Feb 20 '22 at 01:10
  • @ferdy All the games have it. Creation time is not start time. If you need a rated game with a forty minute disparity between creation and start, you can perform the same test I did: create the game (challenge a friend). Once it's accepted, wait 40 minutes to make the first moves. Or see the game Dargscisyhp used as an example. Game created at 1:25:40 remaining in tourney. 6s elapse between creation and game start. 22s elapse during play. But using the berserk API shows 27.5s between creation and final move. – L. Scott Johnson Feb 20 '22 at 02:33
  • Lichess has a system to limit the first move wait time to 24s. If first move is not sent the game is aborted. – ferdy Feb 20 '22 at 03:04
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/134286/discussion-between-l-scott-johnson-and-ferdy). – L. Scott Johnson Feb 20 '22 at 03:08
1

I tried to get some data from chess.com using their published api. I use the game archive endpoint to get the data, and use the leaderboard endpoint to get the top 16 players in bullet.

Typical game header in the pgn from pgn key of the returned request response.

[Event "Live Chess"]
[Site "Chess.com"]
[Date "2020.09.07"]
[Round "-"]
[White "DexyDex"]
[Black "Orange_Ghost"]
[Result "1-0"]
[CurrentPosition "1Q3B2/1R6/5p1k/3q1P1p/1P1N2pP/2P1r3/6PK/8 b - -"]
[Timezone "UTC"]
[ECO "C64"]
[ECOUrl "https://www.chess.com/openings/Ruy-Lopez-Opening-Classical-Defense-4.O-O-d6"]
[UTCDate "2020.09.07"]
[UTCTime "21:54:44"]
[WhiteElo "2839"]
[BlackElo "2849"]
[TimeControl "60"]
[Termination "DexyDex won by checkmate"]
[StartTime "21:54:44"]
[EndDate "2020.09.07"]
[EndTime "21:56:43"]
[Link "https://www.chess.com/game/live/5413100443"]

Elapse time can be calculated using the EndTime and StartTime tag values.

Top bullet players

['Alexander_Zubov', 'AnishGiri', 'Chefshouse',
'DanielNaroditsky', 'GMWSO', 'Hikaru', 'Indianlad',
'nihalsarin', 'NikoTheodorou', 'Oleksandr_Bortnyk',
'Orange_Ghost', 'RaunakSadhwani2005', 'SmurferHe',
'spicycaterpillar', 'wonderfultime', 'Zhigalko_Sergei']

Sample datasets

   elapse_sec  tc                            termi                                         link
0         142  60             chessawp won on time   https://www.chess.com/game/live/3359644035
1         123  60             chessawp won on time   https://www.chess.com/game/live/3359654153
2         105  60  TrimitziosP7 won by resignation  https://www.chess.com/game/live/38857280443
3         157  60         TrimitziosP7 won on time  https://www.chess.com/game/live/38857733457
4         111  60     denizozen won by resignation  https://www.chess.com/game/live/38866804411
       elapse_sec  tc                               termi                                        link
19406         144  60         Zhigalko_Sergei won on time  https://www.chess.com/game/live/2793173203
19407          52  60  Zhigalko_Sergei won by resignation  https://www.chess.com/game/live/2793177225
19408         131  60    Zhigalko_Sergei won by checkmate  https://www.chess.com/game/live/2831119397
19409         109  60          Kanallija won by checkmate  https://www.chess.com/game/live/2831121747
19410         120  60        Kanallija won by resignation  https://www.chess.com/game/live/2831123668

Figures

datasets: 19411
mean: 108
stdev: 35

Distribution plot

enter image description here

ferdy
  • 3,795
  • 5
  • 18