Last sum­mer my fam­ily got together for a nice week­end at the lake and planned on hav­ing a Euchre tour­na­ment since we are big on the game. I vol­un­teered to throw together a nice lit­tle GUI to keep track of it all. I had it all planned out in my head and con­tin­u­ally put it off until it was about an hour before we were going to start. After strug­gling with some ini­tial asso­cia­tive arrays, I quickly ran to a pad and paper to get things orga­nized. I vowed last July to put together some­thing spec­tac­u­lar for the year to come and here it is next July and I am finally fin­ished!

So here’s how I did it:

 

<SCRIPT language=“javascript” type=“text/javascript”>

<!–

// global variables

var teams;

var ini­tRound = 0;

var rounds = 0; // total to be defined later

 

func­tion randOrd(){ 

return (Math.round(Math.random())-0.5); 

}

 

These are some glob­als that I reuse such as ‘teams’ which is the asso­cia­tive array that holds all the team info like name, play­ers, record, and points. The ‘ini­tRound’ is used to deter­mine which round it is so I can do stuff like update the score, move com­pleted rounds, and tell when the tour­na­ment is done. Obvi­ously ’rounds’ is how many rounds there will be and by my com­ment will be defined later. The ‘ran­dOrd()’ func­tion I ripped from Google and is used to ran­dom­ize the play­ers into teams.

 

func­tion promptNumPlayers() {

var numPlay­ers = 0;

numPlay­ers = prompt(“How many players?”,”(enter num­ber here)”);

if (numPlay­ers == null) {

return;

}

if(isNaN(parseInt(numPlayers)) || numPlay­ers < 0 || numPlay­ers == 0) {

alert(“Looking for a num­ber here, chief! Try again!”);

prompt­NumPlay­ers();

} else if(numPlayers % 2) {

alert(“Need even num­ber of play­ers for this Euchre tournament!”);

prompt­NumPlay­ers();

} else {

buildTeams(numPlayers);

}

}

 

This is what starts it all off — have to know how many play­ers you’re going to have! The check for ‘null’ sees if the user pressed ‘Can­cel’ instead of ‘OK’. This func­tion loads every time the page does so if I or some­one else doesn’t want to go through the setup, this will stop it and just dis­play the basics (good for test­ing CSS). Next, we see if the user tried to be smart and put “twelve” instead of “12” or some­thing else that would crash the script. Since I don’t know any­one with a robot that plays Euchre, I made them pick an even num­ber so proper teams of two could be formed. Speak­ing of:

func­tion buildTeams(num) {

numTeams = (num / 2)

teams = new Array(numTeams);

play­ers = new Array(num);

 

//build play­ers list

for (i=0;i<num;i++) {

players[i] = prompt(“What is player #”+ (i+1) + “‘s name?”,”(enter name here)”);

if(players[i] == “” || players[i] == “(enter name here)”) { 

alert(“Please enter a proper name!”);

players[i] = prompt(“What is player #”+ (i+1) + “‘s name?”,”(enter name here)”);

}

}

 

players.sort( ran­dOrd ); // ran­dom­ize players

 

//build teams and stats

for (i= 0; i < numTeams; i++) {

teams[i] = new Array(6); 

teams[i][0] = (i+1); // team name

teams[i][1] = players.pop(); // team­mate 1

teams[i][2] = players.pop(); // team­mate 2

teams[i][3] = 0; // wins

teams[i][4] = 0; // losses

teams[i][5] = 0; // points total

}

 

buildSchedule(teams);

dis­playTeams();

}

The num­ber of play­ers is divided by two to form those teams of two (Euchre is tra­di­tion­ally played with part­ners incase you didn’t know). The ‘teams’ and ‘play­ers’ arrays are set to proper length and then the prompt­ing for player names begins. Again, more check­ing to make sure the user enters some­thing besides blank or the stan­dard filler text. Here’s where that ran­dom func­tion from ear­lier comes in handy. It jum­bles the array so now I can go through in order and no one knows who will be on whose team. The ‘teams’ array is now built with team name, team­mate 1, team­mate 2, wins, losses, and point total. Time to build the round robin schedule!

func­tion buildSchedule(arr) {

var len = arr.length ;

 

if (len % 2) {

//alert(“Odd num­ber of teams.”);

byeTeam = [“BYE”,“BYE”,“BYE”,0,0,0];

len = arr.push(byeTeam);

}

 

rounds = len — 1;

var games = len / 2;

//alert(“There are ” + rounds + ” rounds to play and there will be ” + games + ” games each round.”);

var home = new Array(games);

var away = new Array(games);

 

//establish home and away

for (i = 0; i < games; i++) {

home[i] = arr[i][0];

away[i] = arr[i + games][0];

}

 

away.reverse(); // cre­ates first vs last … mid­dle vs mid­dle matchups

 

for(r = 0; r < rounds; r++) {

var txtN­ode = document.createTextNode(“Round ” + (r+1) + “:”);

 

var round­Num = document.createElement(‘h5’);

roundNum.appendChild(txtNode);

 

var round­Div = document.createElement(‘div’);

roundDiv.setAttribute(‘id’, r);

roundDiv.appendChild(roundNum);

 

// print matchups

for (i=0;i < games; i++) {

txtN­ode = document.createTextNode(“Team ” + away[i]);

var spanA = document.createElement(‘span’);

spanA.id = away[i];

spanA.appendChild(txtNode);

 

txtN­ode = document.createTextNode(“Team ” + home[i]);

var spanH = document.createElement(‘span’);

spanH.id = home[i];

spanH.appendChild(txtNode);

 

txtN­ode = document.createTextNode(“ vs ”);

var matchup = document.createElement(‘p’);

matchup.appendChild(spanA);

matchup.appendChild(txtNode);

matchup.appendChild(spanH);

 

roundDiv.appendChild(matchup);

}

 

document.getElementById(‘schedule’).appendChild(roundDiv);

 

//advance to next round

home.splice(1,0,away.shift());

away.push(home.pop());

}

}

This part baf­fled me the most and made me do a lit­tle research into how Round Robin Tour­na­ments were formed. This is also the first time I had played with the DOM using JavaScript. The ‘(len % 2)’ uses the mod­ulo to test if the num­ber of teams is odd or even. Round Robin tour­na­ments need an even num­ber so if it is odd you have to add in BYE rounds (thus the push­ing of fake team on array). The global ’rounds’ from ear­lier is now defin­able along with how many games will be in each round.

 

Decid­ing how to form match-ups was another dif­fi­culty. I lit­er­ally drew out tour­na­ments and how they pro­gressed and then switched to insert­ing them in Excel. Once I found a pat­tern I decided it would be eas­i­est to split the teams into ‘home’ and ‘away’. The first half of the teams would go to ‘home’ and the sec­ond half to ‘away’. By revers­ing the ‘away’ array (say that 5 times fast), the first team in the ‘teams’ array would now face the last team instead of the mid­dle team (impor­tant later). Now, we build some ele­ments and append chil­dren. The for loop basi­cally says, “I’m cre­at­ing a span for the home and away team, plac­ing the text ‘Team’ at the begin­ning and then assign­ing the id of the home and away team to the span.” This id is actu­ally the same id as the ‘teams’ array which will come in handy later when we are updat­ing scores. 

 

Based on what I found draw­ing out tour­na­ment rota­tions by hand, the first team would stay in a fixed posi­tion and all the other teams would rotate around it. To accom­plish this, I first spliced in the last array ele­ment of ‘away’ after the first ele­ment of ‘home’ and then popped off the last ele­ment of ‘home’ and pushed it to the front of ‘away’. So some­thing like:

 

 

 


1 6   1 5   1 4
2 5   6 4   5 3
3 4   2 3   6 2

I was pretty proud I fig­ured that out since I was always ter­ri­ble with push, pop, and splice. Next is the easy part of dis­play­ing the teams in a score­board fashion:

func­tion displayTeams() {

var myTable = document.createElement(‘table’);

var myT­HEAD = document.createElement(‘thead’);

var myT­FOOT = document.createElement(‘tfoot’);

var myT­BODY = document.createElement(‘tbody’);

var myRow = document.createElement(‘tr’);

 

var theads = [“Team”,“Player 1″,“Player 2″, “Won”, “Loss”,“Points”];

 

for (i=0;i<theads.length;i++) {

txt = document.createTextNode(theads[i]);

var myTH = document.createElement(‘th’);

myTH.appendChild(txt);

myRow.appendChild(myTH);

}

 

myTHEAD.appendChild(myRow);

myTable.appendChild(myTHEAD);

txt = document.createTextNode(“Results after ” + ini­tRound + ” rounds.”);

td = document.createElement(‘td’);

td.colSpan = 6;

td.appendChild(txt);

tr = document.createElement(‘tr’);

tr.appendChild(td);

myTFOOT.appendChild(tr);

myTable.appendChild(myTFOOT);

 

for (var i= 0; i< teams.length; i++) {

arrRows = new Array(); //holds row info so then I can append in a loop

arrRows.push(teams[i][0]); // team number

arrRows.push(teams[i][1]); // player 1

arrRows.push(teams[i][2]); // player 2

arrRows.push(teams[i][3]); // wins

arrRows.push(teams[i][4]); // losses

arrRows.push(teams[i][5]); // points

 

myRow = document.createElement(‘tr’);

 

if(arrRows[0] != “BYE”) {

for(j=0;j<arrRows.length;j++) {

txt = document.createTextNode(arrRows[j]);

myTD = document.createElement(‘td’);

myTD.appendChild(txt);

myRow.appendChild(myTD);

}

}

 

myTBODY.appendChild(myRow);

}

 

myTable.appendChild(myTBODY);

document.getElementById(‘scoreboard’).appendChild(myTable);

 

update­Form();

}

Again, build­ing ele­ments at the DOM level, append­ing chil­dren, form­ing the table’s head, etc. The footer was more to make sure it was actu­ally updat­ing but actu­ally added a nice touch in the end. Loop through the teams, give each ele­ment a cell, tada! But wait, I don’t care about that fake BYE team I had to throw in to make a sched­ule so don’t print that. These last two func­tions took me the longest time and the most debugging:

func­tion updateForm() {

cur­Round­Div = document.getElementById(initRound);

scor­ing­Div = curRoundDiv.cloneNode(true);

document.getElementById(‘curMatch’).appendChild(scoringDiv);

 

points = document.createElement(‘select’);

for(i=0;i<=13;i++) {

opt = document.createElement(‘option’);

optScore = document.createTextNode(i);

opt.appendChild(optScore);

points.appendChild(opt);

}

 

spanAH = scoringDiv.getElementsByTagName(‘span’);

 

for(i=0;i<spanAH.length;i++) {

if(spanAH[i].id != “BYE”) {

PTS = points.cloneNode(true);

PTS.id = spanAH[i].id;

spanAH[i].appendChild(PTS);

}

}

}

Pretty short, right? That ‘ini­tRound’ comes in handy again! Pull what­ever round it is, clone it and let’s make it into an update form. Build sim­ple ‘selects’ with ‘options’ to num­ber from 0 to 13. Why 13? I thought Euchre games were played to 10? I’m not going to short change some­one who goes alone after they’re in the barn! Besides, points are the tie break­ing mech­a­nism in this beast so points need to be awarded where due. Still no love for the BYE team as they aren’t awarded a ‘select’. Now for makes this sucker run:

func­tion nextRound(form) {

var form­S­e­lects = form.getElementsByTagName(‘select’);

 

//update W, L, point totals

for(i=0;i<formSelects.length;i++) {

for(j=0;j<teams.length;j++) {

//alert(“team: ” + teams[j][0] + ” | selec­tID: ” + formSelects[i].id);

if(teams[j][0] == formSelects[i].id) {

//alert(“MATCH!”);

teams[j][5] += formSelects[i].selectedIndex;

if(formSelects[i].selectedIndex >= 10) {

//alert(teams[j][0] + ” team is winner!”);

teams[j][3] += 1;

} else {

//alert(teams[j][0] + ” team is loser!”);

teams[j][4] += 1;

}

}

}

}

 

var cur­Round­Div = document.getElementById(initRound);

var spanAH = curRoundDiv.getElementsByTagName(‘span’);

 

for(i=0;i<spanAH.length;i++) {

if(spanAH[i].id != “BYE”) {

for(j=0;j<formSelects.length;j++) {

//alert(“spanID: ” + spanAH[i].id + ” | selec­tID: ” + formSelects[j].id);

if(formSelects[j].id == spanAH[i].id) {

//alert(“MATCH!”);

txt = document.createTextNode(“ Scored: ” + formSelects[j].selectedIndex);

spanAH[i].appendChild(txt);

}

}

}

}

 

document.getElementById(‘pastRounds’).appendChild(curRoundDiv);

 

//remove old scoreboard

d = document.getElementById(‘scoreboard’);

t = d.getElementsByTagName(‘table’);

d.removeChild(t[0]);

 

//remove old update form

d = document.getElementById(‘curMatch’);

i = d.getElementsByTagName(‘div’);

d.removeChild(i[0]);

 

ini­tRound++;

 

if ( ini­tRound == rounds) {

te = teams.pop();

if(te[0] == “BYE”) {

//alert(“ODD TEAMS”);

for(i=0;i<teams.length;i++) {

teams[i][4] -= 1; //subtract loss from BYE rounds

}

}

dis­playTeams();

document.getElementById(‘updateForm’).style.display = “none”;

txt = document.createTextNode(“Play Again?”);

again­But = document.createElement(‘a’);

againBut.setAttribute(‘href’,‘JavaScript:location.reload(true);’);

againBut.appendChild(txt);

document.getElementById(‘schedule’).appendChild(againBut);

} else {

dis­playTeams();

}

}

Scary at first, I’ll admit, but it’s really just doing a lot and is pretty sim­ple. First, find all the ‘selects’ we just inserted and loop through them for their ‘selecte­dIn­dex’ which also hap­pens to be the score awarded (0,1,2,…). The ‘selects’ were sneak­ily given the same id as the spans from ear­lier. The spans have the same id as the teams so now these selects have the teams’ id, too! This makes it easy to fig­ure out what score goes to what team. I added some alerts dur­ing the debug process and now they are like nice com­ments. So the scores are updated in the ‘teams’ array, let’s print the scores next to the team so we can review what each team got dur­ing each round. Wouldn’t want the com­puter mak­ing a com­pu­ta­tional error! Next, let’s move the played rounds to a new div called ‘pas­tRounds’. It’ll show the score and grey it out while allow­ing the next round to dis­play at the top of the list and page. This was some­what of a pain to fig­ure out, but paid off as a great les­son manip­u­lat­ing the DOM with JavaScript.
After the small stuff, we need to update the big stuff like the score­board and update form. I thought it was eas­ier to just delete the old stuff and rerun the func­tions I already had to build new ones. The ‘ini­tRound’ is increased to say, “Next round!” and then checked to make sure it wasn’t the last round. If it was NOT the last round the ‘dis­playTeams()’ is called, gets new data from ‘teams’ to dis­play new scores, and then calls ‘update­Form()’ to print new ‘selects’ for the next round’s match-ups.
If it is the last round, we remove the update form to sig­nify no more scores will be accepted and add a link to Play Again (or refresh). I found out that BYE rounds resulted in the team in a BYE round receiv­ing a loss so at the very end I go through and sub­tract a loss from every­one if there was a ‘BYE’ team in the ‘teams’ array. I wrote this expla­na­tion out in the footer so every­one can understand.
From there it’s a mat­ter of apply­ing a lit­tle CSS and arrang­ing the DIVs how you like. If you want to see that code just visit the page and view source. It’s all there! I don’t want every blog post to be this long, but I wanted to write this out so I at least knew what I did when/if I look back on this. If you think this could have been done any eas­ier I’d love to hear it in the comments!