const yellow = 'rgb(255, 255, 0)'; var timerNotStarted = true; //var initBoardStr = ""; var heardFromAllPlayers=false; //did all players check in? var allSubjectsReady=false; //did one person get all player checkins? var gameState=[]; //player ready choices //Specify the person-channel network. {player 1: [channel 1, channel 2, ...], ...} //The keys are the player number and the items in the list are the channels they are on. var chat_channel_matrix = {} //Create global variables var dictFlashIntervals = {}; var chat_channel_count = '' // var curOpenChannel = ''; // var auto_scroll_chat = false; //does chat scroll to most recent message when you open the channel? var chat_show_empty_prompt = true; //should we show instructions if the chat is empty? var chat_max_channels_per_column = 10; //layout variable - number of channels to show in each column var chat_flash_interval = 400; //how frequently chat channel flashes when there's a message (in milliseconds) var brainstorming_channel = 1; var other_talk_channel = 2; var brainstormingChannelName = "Brainstorming Channel"; var otherTalkChannelName = "Other Channel"; function buildInitialState() { let difficulty = variables['difficulty_level']; //use study parameter let unique = true; // note - uniqueness not implemented per documentation let initBoardStr = sudoku.generate(difficulty, unique); return initBoardStr; } function initChat() { if (variables['show_chat'] == 'true') { $('#chat').show(); } else { $('#chat').hide(); } } function createChannelMatrix() { let key = 0; for (let i = 0; i < numPlayers; i++) { // for each player create channel in matrix key = (i+1).toString(); console.log(key); //chat_channel_matrix[key] = [brainstorming_channel, other_talk_channel]; chat_channel_matrix[key] = [brainstorming_channel]; } } function initialize() { initChat(); setInitAndSolvedBoards(initialState); displayInitialBoard(); $('.square').change(function() { cellColor = $(this).css("background-color"); cellVal = $(this).html(); if (cellColor != yellow) { // prevent overwriting original board submit($(this).attr('id') + $(this).val()); // submit my move } }); //updateUI(); createChannelMatrix(); console.log(chat_channel_matrix); setupChatChannels(chat_channel_matrix); //function creates the chat network } function startTimer() { //alert("starttimer 1"); setCountdown("timer", variables['countdown_in_seconds']); //alert("starttimer 2"); setInterval(advanceCountdowns, 1); //alert("starttimer 3"); //updateUI(); //alert("starttimer 4"); } // countdown library has parameter values and calls this on each update so if need // can have access to the parameters to help perform some action // - like 10, 30 second warning? function countdownUpdate(id, diff, clockString) { } function countdownExpired(id) { $(".countdown-text").text('Time is up!'); submit('000'); } function timerFinished() { countdownText = $(".countdown-text").text(); return (countdownText == 'Time is up!'); } function toggleRules() { if ($('#rulesbtn').text() == 'Show Rules') { $('#rules').show(); $('#rulesbtn').text('Hide Rules'); } else { $('#rules').hide(); $('#rulesbtn').text('Show Rules'); } } function toggleChat() { if ($('#chatbtn').text() == 'Show Chat') { $('#container-fluid').show(); $('#chatbtn').text('Hide Chat'); } else { $('#container-fluid').hide(); $('#chatbtn').text('Show Chat'); } } function displayBoardStrInRowsToConsole(boardStr, boardDesc) { console.log(boardDesc); let rowLength = INITIALBOARD.length; let numRows = rowLength; for (let i = 0; i < numRows; i++) { let rowStartPos = i * rowLength; let boardRowStr = boardStr.substr(rowStartPos, rowLength); console.log(boardRowStr); } } function convertBoardStrToJSON(boardStr) { // in format: "23.94.67.8..3259149..76.32.1.....7925.321.4864..68.5317..1....96598721433...9...7" //NOTE: changed replaceAll to replace with /g to do all because some old versions of browsers to not allow replaceAll //boardStr = boardStr.replaceAll('.', '0'); boardStr = boardStr.replace(/\./g, '0'); // use the INITIALBOARD in own .js file as structure for JSON board let JSONBoard = JSON.parse(JSON.stringify(INITIALBOARD)); let boardLen = JSONBoard.length; for(let i = 0; i < boardLen; i++) { let rowStart = i * boardLen; let rowCells = boardStr.substr(rowStart, boardLen); JSONBoard[i].cells = rowCells; } return JSONBoard; } function setInitAndSolvedBoards(initBoardStr) { //alert(initBoardStr); INITIALBOARD = convertBoardStrToJSON(initBoardStr); let solutionBoardStr = sudoku.solve(initBoardStr); if (variables['show_solution_in_console'] == 'true') { //displayBoardStrInRowsToConsole(initBoardStr, "INITIAL BOARD:"); displayBoardStrInRowsToConsole(solutionBoardStr, "SOLVED BOARD:"); } SOLVEDBOARD = convertBoardStrToJSON(solutionBoardStr); } function displayInitialBoard() { for(let i = 0; i < INITIALBOARD.length; i++) { let rowId = INITIALBOARD[i].rowId; let rowCells = INITIALBOARD[i].cells; for(let j = 1; j < rowCells.length+1; j++) { let rowPos = j.toString(); let cellId = rowId + rowPos; let cellVal = rowCells[j-1]; if (cellVal != '0') { $('#'+cellId).css('backgroundColor', 'yellow'); $('#'+cellId).val(cellVal); $('#'+cellId).prop('disabled', true); } } } //updateUI(); } function getCurrBoard() { let currBoard = JSON.parse(JSON.stringify(INITIALBOARD)); for(let i = 0; i < currBoard.length; i++) { let rowId = currBoard[i].rowId; let rowLen = currBoard[i].cells.length; currBoard[i].cells = ""; for(let j = 1; j < rowLen+1; j++) { let rowPos = j.toString(); let cellId = rowId + rowPos; let cellVal = $('#'+cellId).val(); if (cellVal === "") { cellVal = '0'; } currBoard[i].cells = currBoard[i].cells + cellVal; } } return currBoard; } function JSONBoardToString(JSONBoard) { let boardStr = ""; for(let i = 0; i < JSONBoard.length; i++) { boardStr = boardStr + JSONBoard[i].cells; } return boardStr; } function boardIsFinished(JSONBoard) { let boardStr = JSONBoardToString(JSONBoard); let boardFinished = !boardStr.includes('0'); return boardFinished; } function validOneToNineStr(testStr) { let orderedStr = testStr.split('').sort().join(''); let valid1To9Str = (orderedStr == '123456789'); return valid1To9Str; } function validOneToNineStrArray(testStrArray) { let valid1To9StrArray = true; for(let i = 0; i < testStrArray.length; i++) { if (!validOneToNineStr(testStrArray[i])) { valid1To9StrArray = false; break; } } return valid1To9StrArray; } function getRowStrArray(JSONBoard) { rowStrArray = Array(JSONBoard.length).fill(""); for(let i = 0; i < JSONBoard.length; i++) { rowStrArray[i] = JSONBoard[i].cells; } return rowStrArray; } function getColStrArray(JSONBoard) { colStrArray = Array(JSONBoard.length).fill(""); for(let i = 0; i < JSONBoard.length; i++) { for(let j = 0; j < JSONBoard.length; j++) { colStrArray[i] = colStrArray[i] + JSONBoard[j].cells.substr(i, 1); } } return colStrArray; } function getSubSqr(JSONBoard, sqrNum) { let subSquare = ""; let rowStart = 0; let strStart = 0; if (sqrNum >= 6) { rowStart = 6; } else if (sqrNum >= 3) { rowStart = 3; } for(let i = 0; i < 3; i++) { row = rowStart + i; strStart = (sqrNum - rowStart) * 3; subSquare = subSquare + JSONBoard[row].cells.substr(strStart, 3); } return subSquare; } function getSqrStrArray(JSONBoard) { sqrStrArray = Array(JSONBoard.length).fill(""); for(let i = 0; i < JSONBoard.length; i++) { sqrStrArray[i] = getSubSqr(JSONBoard, i); } return sqrStrArray; } function validSudokuBoard(JSONBoard) { let validSudokuBrd = false; let rowStrArray = getRowStrArray(JSONBoard); if (validOneToNineStrArray(rowStrArray)) { let colStrArray = getColStrArray(JSONBoard); if (validOneToNineStrArray(colStrArray)) { let sqrStrArray = getSqrStrArray(JSONBoard); validSudokuBrd = validOneToNineStrArray(sqrStrArray); } } return validSudokuBrd; } function solvedStatus() { currBoard = getCurrBoard(); let returnVal = 'unfinished'; if (timerFinished()) { returnVal = 'timedout'; } else if (JSON.stringify(currBoard) === JSON.stringify(SOLVEDBOARD)) { returnVal = 'solved'; } else if (validSudokuBoard(currBoard)) { // in case non-unique solution returnVal = 'solved'; } else if (boardIsFinished(currBoard)) { returnVal = 'finished/unsolved'; } return returnVal; } function eachTimeNewMove(val) { let cellId = val[0] + val[1]; let cellVal = val[2]; if (cellVal == '0') { cellVal = ""; } $('#'+cellId).val(cellVal); //if (timerNotStarted) { // startTimer(); timerNotStarted = false; //} updateUI(); } function updateUI() { if (solvedStatus() == 'solved') { $('#instruction').html('Sudoku Puzzle Solved! Return to waiting room'); experimentComplete(); return; } else if (solvedStatus() == 'timedout') { $('#instruction').html('Time is up! Return to waiting room'); experimentComplete(); return; } else if (solvedStatus() == 'finished/unsolved') { $('#instruction').html('At least one value on board is incorrect.'); } else { $('#instruction').html('Try to solve Sudoku Puzzle.'); } } // to make sure only one digit is typed in a square, which // is not prevented by min/max alone in html function limit(element, max) { var max_chars = max; if(element.value.length > max_chars) { element.value = element.value.substr(0, max_chars); } } // added function newMove(participant, index){ //alert("got to new move"); fetchMove(participant, currentRound, index, function (val) { if (val.toLowerCase().includes('subject ready')){ checkReady(participant,1); }else if (val=='all subjects ready'){ allSubjectsReady=true; } else { eachTimeNewMove(val); }}); } function checkReady(participant,val) { //if we haven't heard from anyone yet, create array, else add to it. if (gameState.length item !== null).length){ //if everyone has checked in heardFromAllPlayers=true; } } //called at the very beginning of the study function subjectReady(){ $("#begin_button").hide(); //Hide buttons $('#begin_waiting').show(); //Show waiting message submit("Subject ready"); //check to see if everyone is ready until they are. var myVar = setInterval(function (){ console.log('checking for ready updates'); if (allSubjectsReady){ //if I have been told we're ready startExperiment(myVar); } else if (heardFromAllPlayers){ //if I've received all the data var lower=0; for (var i = 1; i < myid; i++) { //check if I'm the lowest person if (activePlayers[i]){ lower=1; } } if (lower==0){ //if so, I tell everyone we're ready submit('all subjects ready'); return; //exit the script } }else{ checkReady(myid,1); } },2000); } function startExperiment(myVar) { clearInterval(myVar); //end the timer setRound(101); initChat(); $('#instruction').hide(); $('#begin_waiting').hide(); // hide waiting message //alert ("got right before sudoku show"); $('#sudokuid').show(); //alert ("got right after sudoku show"); //buildInitialBoard(); //initializeBoard(initBoardStr); startTimer(); } // Call this in initialize to setup the channels. // It takes the css ids you want to target for the chat window selectors and the chat windows themselves function setupChatChannels(dictChatMatrix) { console.info("setChatMatrix() - me: " + myid + ", matrix: " + JSON.stringify(dictChatMatrix)); //get the number of channels to figure out how many to create in the interface var curMaxChannel = 1; for (var participant in dictChatMatrix) { //for each participant var tempArr = dictChatMatrix[participant]; for (var i = 0; i < tempArr.length; i++) { // for each channel the person is on var val = tempArr[i]; if (val > curMaxChannel) { curMaxChannel = val; } } } chat_channel_count = curMaxChannel; var colCount = Math.ceil(chat_channel_count / chat_max_channels_per_column); var channelsPerCol = Math.ceil(chat_channel_count / colCount); var divChatChannels = $('#divChatChannels'); divChatChannels.empty(); var tblChatChannelsHtml = '\r\n' + ' \r\n'; for (var i = 1; i <= colCount; i++) { // The channel listing and open buttons will go in a \r\n'; } tblChatChannelsHtml += ' \r\n' + '
\r\n' + '
    \r\n' + '
    '; var tblChatChannels = $(tblChatChannelsHtml); var divChatWindows = $('#divChatWindows'); divChatWindows.empty(); if (chat_show_empty_prompt) { var divEmptyChatWindow = $( '
    ' + ' Click on a Chat Channel to Open a Chat Window' + '
    ' ); divChatWindows.append(divEmptyChatWindow); } for (var i = 0; i < chat_channel_count; i++) { var curCol = Math.floor(i / channelsPerCol) + 1; var channelId = i + 1; let chatChannelName = brainstormingChannelName; if (channelId == other_talk_channel) { chatChannelName = otherTalkChannelName; $('#divChatHeaderId').html(otherTalkChannelName); } // setup the channel listing as a new
  • var li = $('
  • ' + chatChannelName + '
  • '); li.click(function () { // clicking the element opens the corresponding chat channel openChatChannel($(this).attr('channelId')); }); tblChatChannels.find('#ulChatChannels'+curCol).append(li); // create the actual chat window var chatWindowHtml = '
    '; chatWindowHtml += '
    '; chatWindowHtml += ' ' + chatChannelName + ''; chatWindowHtml += ' X'; chatWindowHtml += '
    '; chatWindowHtml += '
    '; chatWindowHtml += '
    '; chatWindowHtml += '
      '; chatWindowHtml += '
      '; chatWindowHtml += '
      '; chatWindowHtml += '
      '; chatWindowHtml += '
      '; chatWindowHtml += ' '; chatWindowHtml += '
      '; chatWindowHtml += ' '; chatWindowHtml += '
      '; chatWindowHtml += '
      '; chatWindowHtml += '
      '; chatWindowHtml += '
      '; var divChatWindow = $(chatWindowHtml); // enter key keypress function (to send chat). // if you hold down the ctrl+enter does a new line divChatWindow.find('.chatSendField').keypress(function (event) { if (event.which == 13 || event.which == 10) { var sendField = $(this); if (event.ctrlKey) { var val = sendField.val(); sendField.val(val + "\n"); } else { sendChatMsg(sendField.val(), sendField.attr('channelId')); sendField.val(""); event.preventDefault(); } } }); // setup the send button click as well divChatWindow.find(".sendChat").click(function () { var sendField = $('.chatSendField[channelId="' + $(this).attr('channelId') + '"]'); sendChatMsg(sendField.val(), sendField.attr('channelId')); sendField.val(""); }); // add the close chat capability divChatWindow.find(".closeChatWindow").click(function () { $('.divChatWindow').hide(250); curOpenChannel = null; if (chat_show_empty_prompt) { $('#divEmptyChatWindow').show(500); } //$('#divOpenChat').show(500); }); $('#divChatWindows').append(divChatWindow); } divChatChannels.append(tblChatChannels); $('.divChatWindow').hide(); if (chat_show_empty_prompt) { $('#divEmptyChatWindow').show(); } var ulChatChannels = $('#ulChatChannels'); if (dictChatMatrix[myid] != null) { for (var i = 0; i < dictChatMatrix[myid].length; i++) { var channelId = dictChatMatrix[myid][i]; //ulChatChannels.find('li[channelId="'+channelId+'"]').removeAttr('disabled'); tblChatChannels.find('li[channelId="' + channelId + '"]').removeClass('liDisabled'); } } var dictTitles = {}; for (var i = 1; i <= numPlayers; i++) { if (dictChatMatrix[i] != null) { for (var j = 0; j < dictChatMatrix[i].length; j++) { var channelId = dictChatMatrix[i][j]; var chatString = (i == myid ? "(You)" : "Participant " + i); if (dictTitles[channelId] == null) { dictTitles[channelId] = chatString; } else { dictTitles[channelId] += ", " + chatString; } } } } for (var i = 1; i <= chat_channel_count; i++) { tblChatChannels.find('li[channelId="' + i + '"]').attr('title', dictTitles[i]); } } // Opens the chat window for the channel you click function openChatChannel(channelId) { $('.divChatWindow').hide(250); $('.divChatWindow[channelId="' + channelId + '"]').show(500); $('.liChatChannel').removeClass('liSelected'); var liChatChannel = $('.liChatChannel[channelId="' + channelId + '"]'); stopChatFlash(channelId); liChatChannel.addClass('liSelected'); curOpenChannel = channelId; } function stopChatFlash(channelId) { // if the channel was flashing, stop the flash animation if (dictFlashIntervals[channelId] != null) { var liChatChannel = $('.liChatChannel[channelId="' + channelId + '"]'); clearInterval(dictFlashIntervals[channelId]); dictFlashIntervals[channelId] = null; liChatChannel.stop(true, true); while (liChatChannel.hasClass('liChatChannel-Flash')) { liChatChannel.removeClass('liChatChannel-Flash'); } } } //empty the content on a channel (good for multi-round studies but not used in the template) function clearChatWindow(channelId) { var cp = $('.divChatWindow[channelId="' + channelId + '"]'); var cc = cp.find(".chat_content"); cc.empty(); stopChatFlash(channelId); $('.liChatChannel[channelId="' + channelId + '"]').removeClass('liSelected'); if (channelId == curOpenChannel) { $('.divChatWindow').hide(250); if (chat_show_empty_prompt) { $('#divEmptyChatWindow').show(500); } curOpenChannel = 0; } } //empty the content on all channels (good for multi-round studies but not used in the template) function clearAllChatWindows() { $('.chat_content').empty(); $('.liChatChannel').removeClass('liSelected').stop(true, true).removeClass('liChatChannel-Flash'); for (var channelId in dictFlashIntervals) { if (dictFlashIntervals[channelId] != null) { clearInterval(dictFlashIntervals[channelId]); } } $('.divChatWindow').hide(250); if (chat_show_empty_prompt) { $('#divEmptyChatWindow').show(500); } curOpenChannel = null; } // We either sent a chat or received a chat, so append it to the correct window function appendChat(s_from, msg, channelId, clz, bFlash) { var cp = $('.divChatWindow[channelId="' + channelId + '"]'); var cc = cp.find(".chat_content"); var cwrap = cp.find(".cc_wrapper"); while (msg.indexOf('\n') >= 0) { msg = msg.replace('\n', '
      '); } cc.append('
    • ' + ' ' + s_from + ' (' + new Date().toLocaleString('en-US', { hour12: false }) + ') ' + ': ' + msg + '
    • '); if (auto_scroll_chat) { cwrap.stop(); // prevent the animations from queueing up cwrap.animate({ scrollTop: cwrap.prop("scrollHeight") }, 1000); } if (bFlash) { var liChannel = $('.liChatChannel[channelId="' + channelId + '"]'); // if we received a message for a chat window that isn't currently open, add a flash to indicate messages are pending if (channelId != curOpenChannel && dictFlashIntervals[channelId] == null) { var bFlashOn = false; dictFlashIntervals[channelId] = setInterval(function () { if (bFlashOn) { liChannel.removeClass('liChatChannel-Flash', chat_flash_interval); } else { liChannel.addClass('liChatChannel-Flash', chat_flash_interval); } bFlashOn = !bFlashOn; }, chat_flash_interval); } } } // Send a chat message to this channel function sendChatMsg(msg, channelId) { if (msg.length == 0) { return; } sendCom("Chat", -1, { 'channelId': channelId, 'msg': encodeURI(msg) }); //sendChat(msg,[participant]); appendChat("You", msg, channelId, "c_you", false); } // This needs to be called from the main comReceived function function chatReceived(participant, msg, channelId) { var liChannel = $('.liChatChannel[channelId="' + channelId + '"]'); //if this channel is disabled, ignore the message if (liChannel.hasClass('liDisabled')) { return; } // TODO - capture user names? appendChat('Participant ' + participant, msg, channelId, "c_oth", true); } // Handle communication sent function comReceived(index, newCom) { if (newCom.from == myid) return; // This was sent by me...ignore it. switch (newCom.com_type) { case -1: // chat message chatReceived(newCom.from, decodeURI(newCom.to_parts.msg), newCom.to_parts.channelId); break; case -2: // round clues //roundChoices = newCom.to_parts; //setRound(ROUND_PRACTICE); //populateRoundClues(curRound); // these come from participant 1 break; case -3: // clue shared clueReceived(newCom.from, decodeURI(newCom.to_parts.clue), newCom.to_parts.channelId); // clues are a special type of chat...handle them similarly break; } return; }