I was talking to my roommate and good friend about work, and he had an idea for an attraction for an upcoming conference. He wanted to build a jeopardy game, that conference goers could come by, play a quick round on an ipad, and get their score. It is a great idea, because it attracts people, you can offer prizes on top scores, and customers learn more about the company and what we do -- in a fun and interactive way.

Being an Angular.js fanboy, it was my first reaction; and why not? Single page, fast loading, only a few views, easily configurable. But after taking a few looks at the requirements we came up with this list:
  1. Must be able to run entirely locally (conference may not have reliable wifi)
  2. Stand alone scoring (for the above reason, we decided against a player vs player game -- however that will be fun to code, and I may approach it in a later version)
  3. Simple, clean, recognizable as jeopardy
So with those requirements, and a little bit of reflection, I realized that you could do it entirely in Twitter bootstrap and a little bit of jquery. So I set out to build a simple, configurable, jeopardy board.

Building the Board

So the first thing was first: "How do I build a jeopardy board in Twitter Bootstrap?". Immediate reaction was the built-in column system, and maybe use wells for the question blocks. To make it look good, I wrapped it in a .panel and .container. Quick demo as follows:
<div class="container">
<div class="panel panel-default">
	<div class="panel-heading">
		<div class="text-center col-md-2 col-md-offset-1"><h4>History</h4></div> <!-- offset to center odd number of columns -->
		<div class="text-center col-md-2"><h4>Sports</h4></div>
		<div class="text-center col-md-2"><h4>Food</h4></div>
		<div class="text-center col-md-2"><h4>Modern</h4></div>
		<div class="text-center col-md-2"><h4>Encompass</h4></div>
		<div class="clearfix"></div> <!-- clearfix squares the header around the headings. it basically fixes everything -->
	</div>
	<div class="panel-body" id="main-board">
		<div class="category col-md-2 col-md-offset-1">
			<div class="well question">100</div>
			<div class="well question">200</div>
			<div class="well question">300</div>
			<div class="well question">400</div>
		</div>
		<div class="category col-md-2">
			<div class="well question">100</div>
			<div class="well question">200</div>
			<div class="well question">300</div>
			<div class="well question">400</div>
		</div>
		<div class="category col-md-2">
			<div class="well question">100</div>
			<div class="well question">200</div>
			<div class="well question">300</div>
			<div class="well question">400</div>
		</div>
		<div class="category col-md-2">
			<div class="well question">100</div>
			<div class="well question">200</div>
			<div class="well question">300</div>
			<div class="well question">400</div>
		</div>
		<div class="category col-md-2">
			<div class="well question">100</div>
			<div class="well question">200</div>
			<div class="well question">300</div>
			<div class="well question">400</div>
		</div>
	</div>
	<div class="panel-footer">
		<div class="pull-right"><h4>Score: <span id="score">0</span></h4></div>
		<div class="clearfix"></div>
        </div>
</div>
</div>
And a little bit of CSS to clean it up and make it a bit more like a jeopardy board:
.question{
    height:100px;
    vertical-align: middle;
    text-align: center;
    font-size: 1.5em;
    padding-top: 35px;
    cursor: pointer;
}
And that should create a decent looking board, something along the lines of this:

So now that I have the scaffolding for the page, time to add logic to it. I decided to load the questions and answers in a modal window, and have it trigger at onclick on .question. But first I need the questions, and a way of tying the tiles to them. At this point, I decided that i wanted it to be configuration driven. So I could load a json file into it, and have it generate different boards based off of it. So to start, I tore down variable parts of the scaffold.

The updated HTML:
<div class="panel panel-default">
	<div class="panel-heading"></div>
	<div class="panel-body" id="main-board"></div>
	<div class="panel-footer">
                <div class="pull-right"><h4>Score: <span id="score">0</span></h4></div>
                <div class="clearfix"></div>
        </div>
</div>
<div class="modal fade" id="question-modal" role="dialog" aria-labelledby="question-modal" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
        <h4 class="modal-title">Modal title</h4>
      </div>
      <div class="modal-body">
        <p id="question" style="font-size: 1.5em"></p>
      </div>
      <div class="modal-footer ">
        <div id="answers" class="text-center">

        </div>
      </div>
    </div><!-- /.modal-content -->
  </div><!-- /.modal-dialog -->
</div><!-- /.modal -->
Noteably a lot shorter, mostly because javascript will be generating the majority of it from here on out. Note I also added the modal window that we will be loading the questions and answers into.

Now onto some javascript: I bind onlick method to a new class .unanswered, that I will remove when a question is answered. But before we get into the details, here is the structure of json I am using to generate the board:
[//list of categories
    {//category object
        "name":"",
         "questions":[//list of questions
               {//question object
                   "value":100,
                   "question":"",
                   "answers":[//list of answers
                         {//answer object
                              "text":"",
                              "correct":true
                         }
                   ]
               }
          ]
    }
]
Now, finally, onto the javascript:
$(function(){
    $.ajax({//ajax method to load the board.json and call the loadBoard() function on success 
        'async': false,
        'global': false,
        type:'GET',
        dataType:'json',
        url:'board.json',
        success:function(data){
            map = data;
            loadBoard();
        }
    });
    $('.unanswered').click(function(){//event bound to clicking on a tile. it grabs the data from the click event, populates the modal, fires the modal, and binds the answer method
        var category = $(this).parent().data('category');
        var question = $(this).data('question');
        var value = map[category].questions[question].value;
        var answers = $('#answers');
        $('.modal-title').empty().text(map[category].name);
        $('#question').empty().text(map[category].questions[question].question);
        answers.empty();
        $.each(map[category].questions[question].answers, function(i, answer){//loop over the answers and make buttons for each
            answers.append(
                '<button class="btn btn-danger answer" ' +
                    'data-category="'+category+'"' +
                    'data-question="'+question+'"' +
                    'data-value="'+value+'"' +
                    'data-correct="'+answer.correct+'"' +
                    '>'+ answer.text+'</button><br><br>'
            )
        });
        $('#question-modal').modal('show');//fire modal
        console.log(category, question);
        console.log(map[category].questions[question]);
        handleAnswer(); //bind answer onclick to newly created buttons
    });

});
var score = 0;
var map;
function loadBoard(){//function that turns the board.json (loaded in the the map variable) into a jeopardy board
    var board = $('#main-board');
    var columns = map.length;
    var column_width = parseInt(12/columns); //get the width/12 rounded down, to use the bootstrap column width appropriate for the number of categories
    console.log(columns);
    $.each(map, function(i,category){
        //load category name
        var header_class = 'text-center col-md-' + column_width; 
        if (i === 0 && columns % 2 != 0){ //if the number of columns is odd, offset the first one by one to center them

            header_class += ' col-md-offset-1';
        }
        $('.panel-heading').append(
            '<div class="'+header_class+'"><h4>'+category.name+'</h4></div>'
        );
        //add column
        var div_class = 'category col-md-' + column_width;
        if (i === 0 && columns % 2 != 0){
            div_class += ' col-md-offset-1';
        }
        board.append('<div class="'+div_class+'" id="cat-'+i+'" data-category="'+i+'"></div>');
        var column = $('#cat-'+i);
        $.each(category.questions, function(n,question){
            //add questions
            column.append('<div class="well question unanswered" data-question="'+n+'">'+question.value+'</div>')
        });
    });
    $('.panel-heading').append('<div class="clearfix"></div>')

}

function updateScore(){
    $('#score').empty().text(score);
}

function handleAnswer(){
    $('.answer').click(function(){// hide empty the tile, mike it unclickable, update the score if correct, and hide the modal
        var tile= $('div[data-category="'+$(this).data('category')+'"]>[data-question="'+$(this).data('question')+'"]')[0];
        $(tile).empty().removeClass('unanswered').unbind().css('cursor','not-allowed');
        if ($(this).data('correct')){
            score += parseInt($(this).data('value'));
        }
        $('#question-modal').modal('hide');
        updateScore();
    })
}
With a sample board.js, (provided here)here is a board: And there you have it. A functioning, minimalist, javascript jeopardy board. I have a further styled mockup operating at my koding.com page at vm-0.mc706.kd.io/jeopardy/.

Next I will show you how to generate and edit the board.json using less than 100 lines of angular.