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
.
tblChatChannelsHtml +=
'
\r\n' +
'
\r\n' +
'
\r\n';
}
tblChatChannelsHtml +=
'
\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 =
'
';
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('
');
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;
}