text: function setCardImages() {
    for (let i = 0; i < numDecks; i++) {
      // Threes.
      $('#1-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/3/32/Poker-sm-24C-3c.png');
      $('#1-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/d/d4/Poker-sm-23C-3d.png');
      $('#1-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/8/80/Poker-sm-22C-3h.png');
      $('#1-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/2/2c/Poker-sm-21C-3s.png');
      // Fours.
      $('#2-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/9/90/Poker-sm-24B-4c.png');
      $('#2-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/a/ab/Poker-sm-23B-4d.png');
      $('#2-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/7/7a/Poker-sm-22B-4h.png');
      $('#2-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/4/4b/Poker-sm-21B-4s.png');
      // Fives.
      $('#3-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/4/48/Poker-sm-24A-5c.png');
      $('#3-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/4/48/Poker-sm-23A-5d.png');
      $('#3-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/d/d3/Poker-sm-22A-5h.png');
      $('#3-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/8/86/Poker-sm-21A-5s.png');
      // Sixes.
      $('#4-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/1/15/Poker-sm-249-6c.png');
      $('#4-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/7/73/Poker-sm-239-6d.png');
      $('#4-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/7/72/Poker-sm-229-6h.png');
      $('#4-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/0/05/Poker-sm-219-6s.png');
      // Sevens.
      $('#5-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/b/ba/Poker-sm-248-7c.png');
      $('#5-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/c/cd/Poker-sm-238-7d.png');
      $('#5-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/4/43/Poker-sm-228-7h.png');
      $('#5-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/e/e2/Poker-sm-218-7s.png');
      // Eights.
      $('#6-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/6/6e/Poker-sm-247-8c.png');
      $('#6-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/c/c6/Poker-sm-237-8d.png');
      $('#6-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/a/a9/Poker-sm-227-8h.png');
      $('#6-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/e/e6/Poker-sm-217-8s.png');
      // Nines.
      $('#7-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/6/64/Poker-sm-246-9c.png');
      $('#7-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Poker-sm-236-9d.png');
      $('#7-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/d/d6/Poker-sm-226-9h.png');
      $('#7-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/3/30/Poker-sm-216-9s.png');
      // Tens.
      $('#8-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/f/f3/Poker-sm-245-Tc.png');
      $('#8-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/6/6d/Poker-sm-235-Td.png');
      $('#8-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/f/fe/Poker-sm-225-Th.png');
      $('#8-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/5/50/Poker-sm-215-Ts.png');
      // Jacks.
      $('#9-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/2/2f/Poker-sm-244-Jc.png');
      $('#9-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/a/aa/Poker-sm-234-Jd.png');
      $('#9-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/c/ce/Poker-sm-224-Jh.png');
      $('#9-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/5/5e/Poker-sm-214-Js.png');
      // Queens.
      $('#10-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/1/1e/Poker-sm-243-Qc.png');
      $('#10-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/7/70/Poker-sm-233-Qd.png');
      $('#10-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/5/5d/Poker-sm-223-Qh.png');
      $('#10-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/a/a5/Poker-sm-213-Qs.png');
      // Kings.
      $('#11-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/2/25/Poker-sm-242-Kc.png');
      $('#11-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/d/d0/Poker-sm-232-Kd.png');
      $('#11-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/7/79/Poker-sm-222-Kh.png');
      $('#11-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/9/9b/Poker-sm-212-Ks.png');
      // Aces.
      $('#12-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/3/38/Poker-sm-241-Ac.png');
      $('#12-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/b/be/Poker-sm-231-Ad.png');
      $('#12-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/d/d3/Poker-sm-221-Ah.png');
      $('#12-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/3/35/Poker-sm-211-As.png');
      // Twos.
      $('#13-1-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/c/cd/Poker-sm-24D-2c.png');
      $('#13-2-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/b/bb/Poker-sm-23D-2d.png');
      $('#13-3-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/2/20/Poker-sm-22D-2h.png');
      $('#13-4-' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/c/c5/Poker-sm-21D-2s.png');
      // Jokers.
      $('#14--' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/2/23/The_Jolly_Nero.jpg');
      $('#15--' + i).attr('src', 'https://upload.wikimedia.org/wikipedia/commons/d/db/The_Jolly_Rosso.jpg');
    }
  }
  
  function getDifferenceBetweenVisibleAndCardWidths() {
    let windowWidth = window.innerWidth * 0.8;
    let numCards = $('.game-card').length;
    if (numCards <= 1) {
      return 0;
    }
    let cardWidth = $('.game-card-img').width();
    let visibleWidth = (windowWidth - cardWidth) / (numCards - 1);
    return cardWidth - visibleWidth;
  }
  
  function recreateClickableCardTops() {
    $('.clickable-card-top').remove();
    let widthDifference = getDifferenceBetweenVisibleAndCardWidths();
    if (widthDifference <= 0) {
      return;
    }
    let numCards = $('.game-card').length;
    let numClickableDivsCreated = 0;
    $('.game-card-img').each(function() {
      let id = $(this).attr('id') + '-clickable';
      let offset = $(this).offset();
      let lastCard = numClickableDivsCreated === numCards - 1;
      if (lastCard) {
        return false;
      }
      if (!$(this).parent().hasClass('card-selected')) {
        return;
      }
      let nextCardSelected = $(this).parent().nextAll('.card-selected').eq(0);
      let nextCardSelectedLeftOffset = nextCardSelected.length > 0 ? nextCardSelected.offset().left : Number.MAX_SAFE_INTEGER;
      let cardTopWidth = Math.min($(this).width(), nextCardSelectedLeftOffset - offset.left);
      let clickableGameCardTop = $.parseHTML('<div class="clickable-card-top"></div>')[0];
      clickableGameCardTop.id = id + '-top';
      clickableGameCardTop.style.position = 'absolute';
      clickableGameCardTop.style.top = offset.top + 'px';
      clickableGameCardTop.style.left = offset.left + 'px';
      clickableGameCardTop.style.height = '60px';
      clickableGameCardTop.style.width = cardTopWidth + 'px';
      $('body').append(clickableGameCardTop);
      numClickableDivsCreated++;
    });
    $('.clickable-card-top').click(function() {
      let id = $(this).attr('id');
      let i = id.indexOf('-clickable-top');
      let cardId = id.substring(0, i);
      $('#' + cardId + '-clickable').toggleClass('clickable-card-selected');
      $('#' + cardId).parent().toggleClass('card-selected');
      updateButtons();
      recreateClickableCardTops();
    });
  }
  
  function overlapCardsAndCreateClickableDivs() {
    let numCards = $('.game-card').length;
    if (numCards === 0) {
      return;
    }
    let widthDifference = getDifferenceBetweenVisibleAndCardWidths();
    let numMarginsChanged = 0;
    if (widthDifference > 0) {
      $('.game-card').each(function() {
        $(this).css('margin-right', '-' + widthDifference + 'px');
        numMarginsChanged++;
        if (numMarginsChanged == numCards - 1) {
          return false;
        }
      });
    } else {
      $('.game-card').each(function() {
        $(this).css('flex-grow', '');
      });
    }
    $('.clickable-card').remove();
    let overlappedClickableCardWidth = 0;
    if (numCards > 1) {
      let left = -1;
      $('.game-card-img').each(function() {
        let thisLeft = $(this).offset().left;
        if (left < 0) {
          left = thisLeft;
        } else {
          overlappedClickableCardWidth = Math.min($(this).width(), thisLeft - left);
          return false;
        }
      });
    }
    let numClickableDivsCreated = 0;
    $('.game-card-img').each(function() {
      let id = $(this).attr('id') + '-clickable';
      let offset = $(this).offset();
      let height = $(this).height();
      let lastCard = numClickableDivsCreated === numCards - 1;
      let width = lastCard ? $(this).width() : overlappedClickableCardWidth;
      let clickableGameCard = $.parseHTML('<div class="clickable-card"></div>')[0];
      clickableGameCard.id = id;
      clickableGameCard.style.position = 'absolute';
      clickableGameCard.style.left = offset.left + 'px';
      clickableGameCard.style.height = height + 'px';
      clickableGameCard.style.width = width + 'px';
      if ($(this).parent().hasClass('card-selected')) {
        clickableGameCard.classList.add('clickable-card-selected');
        clickableGameCard.style.top = offset.top + 60 + 'px';
      } else {
        clickableGameCard.style.top = offset.top + 'px';
      }
      $('body').append(clickableGameCard);
      numClickableDivsCreated++;
    });
    recreateClickableCardTops();
    $('.clickable-card').click(function() {
      let id = $(this).attr('id');
      let i = id.indexOf('-clickable');
      let cardId = id.substring(0, i);
      $(this).toggleClass('clickable-card-selected');
      $('#' + cardId).parent().toggleClass('card-selected');
      updateButtons();
      recreateClickableCardTops();
    });
  }
  
  function resizeCenter() {
    let gameCenterTop = $('#game-center').offset().top;
    let gameHandTop = $('#game-hand-wrapper').length > 0 ? $('#game-hand-wrapper').offset().top : Number.MAX_SAFE_INTEGER;
    let numPxInRem = parseFloat(getComputedStyle(document.documentElement).fontSize);
    $('#game-center').css('height', Math.min(200, gameHandTop - gameCenterTop - 2 * numPxInRem) + 'px');
  }
  
  function getSelectedCards() {
    let hand = [];
    $('.game-card.card-selected').each(function() {
      let cardInfo = $(this).children('img').attr('id').split('-');
      let deckIndex = parseInt(cardInfo[2]);
      let value = parseInt(cardInfo[0]);
      if (value == 14 || value == 15) {
        hand.push({
          deckIndex: deckIndex,
          value: value,
        });
      } else {
        hand.push({
          deckIndex: deckIndex,
          value: value,
          suit: parseInt(cardInfo[1]),
        });
      }
    });
    return hand;
  }
  
  function getUidOrReload() {
    let hand = getSelectedCards();
    let cookies = document.cookie.split(';');
    for (c of cookies) {
      let keyAndValue = c.trim().split('=');
      if (keyAndValue[0] == 'uid') {
        return parseInt(keyAndValue[1]);
      }
    }
    location.reload();
  }
  
  function updateButtons() {
    let hand = getSelectedCards();
    socket.emit('check', getUidOrReload(), hand.length === 0 ? null : hand);
  }
  
  setCardImages();
  overlapCardsAndCreateClickableDivs();
  resizeCenter();
  updateButtons();
  
  window.addEventListener('resize', function() {
    overlapCardsAndCreateClickableDivs();
    resizeCenter();
  });
  
  $('#send-card').click(function() {
    socket.emit('send card', getUidOrReload(), getSelectedCards());
  });
  
  $('#play').click(function() {
    socket.emit('play', getUidOrReload(), getSelectedCards());
  });
  
  $('#pass').click(function() {
    socket.emit('play', getUidOrReload(), null);
  });
  
  socket.on('num spectators', (data) => {
    $('#spectators').text('Spectators: ' + data.numSpectators);
  });
  
  socket.on('check ok', (data) => {
    if (data.onlyPassOk) {
      $('#play').prop('disabled', true);
      $('#play').prop('title', 'Select cards to play');
    } else {
      $('#play').prop('disabled', false);
      $('#play').removeAttr('title');
    }
    if (data.isPassOk) {
      $('#pass').prop('disabled', false);
      $('#pass').removeAttr('title');
    } else {
      $('#pass').prop('disabled', true);
      $('#pass').prop('title', 'First action of round cannot be pass');
    }
    $('#send-card').prop('disabled', false);
    $('#send-card').removeAttr('title');
  });
  
  socket.on('check error', (data) => {
    $('#play').prop('disabled', true);
    $('#play').prop('title', data.err);
    if (data.err == 'Not your turn') {
      $('#pass').prop('disabled', true);
      $('#pass').prop('title', data.err);
    } else if (data.isPassOk) {
      $('#pass').prop('disabled', false);
      $('#pass').removeAttr('title');
    } else {
      $('#pass').prop('disabled', true);
      $('#pass').prop('title', 'First action of round cannot be pass');
    }
    $('#send-card').prop('disabled', true);
    $('#send-card').prop('title', data.err);
  });
  
  socket.on('game error', (data) => {
    $('body').html(
      '<div class="center" style="text-align:center">' +
      '<h1>Error</h1>' +
      '<br>' +
      data.err +
      '</div>'
    );
  });
  
  // Possible fields:
  // * requestUpdate
  // * title
  // * gamePlayers
  // * gameCenter
  // * gameHand
  socket.on('update', (data) => {
    if (data.requestUpdate) {
      socket.emit('get update', getUidOrReload());
    }
    if (data.hasOwnProperty('title')) {
      $('#title').text(data.title);
    }
    if (data.hasOwnProperty('gamePlayers')) {
      $('#game-players').html(data.gamePlayers);
      resizeCenter();
    }
    if (data.hasOwnProperty('gameCenter')) {
      $('#game-center').html(data.gameCenter);
      setCardImages();
      resizeCenter();
    }
    if (data.hasOwnProperty('gameHand') && $('#game-hand').length > 0) {
      $('#game-hand').html(data.gameHand);
      setCardImages();
      overlapCardsAndCreateClickableDivs();
    }
    updateButtons();
  });
  
  socket.on('tribute summary', (data) => {
    $('body').append(data.tributeModalHtml);
    $('#tributes').modal('show');
    $('#tributes').on('hide.bs.modal', function() {
      location.reload();
    });
  });
  
  socket.on('message', (msg) => {
    if (msg === 'game over') {
      location.reload();
    }
  });
  