Black box

From Rosetta Code
Black box is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Implement a version of the Black Box game beginners configuration: 4 Atoms in an 8 x 8 grid.

Determine where the hidden atoms are in the box, by observing how the light beams fired into the box react when leaving it.
Possible results:
'H': the beam hit an atom and stopped
'R': Either the beam was reflected back the way it came or there was a ball just to one side of its entry point
'Numbers': indicate that the beam entered one of those squares and emerged from the other


Extra credit (Different game types):
-More or less atoms (maybe random)
-Different grid sizes


JavaScript

Play it here. <lang javascript> var sel, again, check, score, done, atoms, guesses, beamCnt, brdSize;

function updateScore( s ) {

   score += s || 0;
   para.innerHTML = "Score: " + score;

} function checkIt() {

   check.className = "hide";
   again.className = "again";
   done = true;
   var b, id;
   for( var j = 0; j < brdSize; j++ ) {
       for( var i = 0; i < brdSize; i++ ) {
           if( board[i][j].H ) {
               b = document.getElementById( "atom" + ( i + j * brdSize ) );
               b.innerHTML = "⚈";
               if( board[i][j].T ) {
                   b.style.color = "#0a2";
               } else {
                   b.style.color = "#f00";
                    updateScore( 5 );
               }
           } 
       }
   }

} function isValid( n ) {

   return n > -1 && n < brdSize;

} function stepBeam( sx, sy, dx, dy ) {

   var s = brdSize - 2
   if( dx ) {
       if( board[sx][sy].H ) return {r:"H", x:sx, y:sy};
       if( ( (sx == 1 && dx == 1) || (sx == s && dx == -1) ) && ( ( sy > 0 && board[sx][sy - 1].H ) || 
           ( sy < s && board[sx][sy + 1].H ) ) ) return {r:"R", x:sx, y:sy};
       if( isValid( sx + dx ) ) {
           if( isValid( sy - 1 ) && board[sx + dx][sy - 1].H ) {
               dx = 0; dy = 1;
           }
           if( isValid( sy + 1 ) && board[sx + dx][sy + 1].H ) {
               dx = 0; dy = -1;
           }
           sx += dx;
           return stepBeam( sx, sy, dx, dy );
       } else {
           return {r:"O", x:sx, y:sy};
       }
   } else {
       if( board[sx][sy].H ) return {r:"H", x:sx, y:sy}; 
       if( ( (sy == 1 && dy == 1) || (sy == s && dy == -1) ) && ( ( sx > 0 && board[sx - 1][sy].H ) || 
          ( sx < s && board[sx + 1][sy].H ) ) ) return {r:"R", x:sx, y:sy};
       if( isValid( sy + dy ) ) {
           if( isValid( sx - 1 ) && board[sx - 1][sy + dy].H ) {
               dy = 0; dx = 1;
           }
           if( isValid( sx + 1 ) && board[sx + 1][sy + dy].H ) {
               dy = 0; dx = -1;
           }
           sy += dy;
           return stepBeam( sx, sy, dx, dy );
       } else {
           return {r:"O", x:sx, y:sy};
       }
   }

} function fireBeam( btn ) {

   var sx = btn.i, sy = btn.j, dx = 0, dy = 0;
   if( sx == 0 || sx == brdSize - 1 ) dx = sx == 0 ? 1 : - 1;
   else if( sy == 0 || sy == brdSize - 1 ) dy = sy == 0 ? 1 : - 1;
   var s = stepBeam( sx + dx, sy + dy, dx, dy );
   switch( s.r ) {
       case "H": 
           btn.innerHTML = "H"; 
           updateScore( 1 );
           break;
       case "R":
           btn.innerHTML = "R";
           updateScore( 1 );
           break;
       case "O":
           if( s.x == sx && s.y == sy ) {
               btn.innerHTML = "R";
               updateScore( 1 );
           }
           else {
               var b = document.getElementById( "fire" + ( s.x + s.y * brdSize ) );
               btn.innerHTML = "" + beamCnt;
               b.innerHTML = "" + beamCnt;
               beamCnt++;
               updateScore( 2 );
           }
   }

} function setAtom( btn ) {

   if( done ) return;
   
   var b = document.getElementById( "atom" + ( btn.i + btn.j * brdSize ) );
   if( board[btn.i][btn.j].T == 0 && guesses < atoms ) {
       board[btn.i][btn.j].T = 1;
       guesses++;
       b.innerHTML = "⚈";
   } else if( board[btn.i][btn.j].T == 1 && guesses > 0 ) {
       board[btn.i][btn.j].T = 0;
       guesses--;
       b.innerHTML = " ";
   }
   if( guesses == atoms ) check.className = "check";
   else check.className = "hide";

} function startGame() {

   score = 0;
   updateScore();
   check.className = again.className = "hide";
   var e = document.getElementById( "mid" );
   if( e.firstChild ) e.removeChild( e.firstChild );
   
   brdSize = sel.value;
   done = false;
   if( brdSize < 5 ) return;
   var brd = document.createElement( "div" );
   brd.id = "board";
   brd.style.height = brd.style.width = 5.2 * brdSize + "vh"
   e.appendChild( brd );
   
   var b, c, d;
   for( var j = 0; j < brdSize; j++ ) {
       for( var i = 0; i < brdSize; i++ ) {
           b = document.createElement( "button" );
           b.i = i; b.j = j;
           if( j == 0 && i == 0 || j == 0 && i == brdSize - 1 ||
               j == brdSize - 1 && i == 0 || j == brdSize - 1 && i == brdSize - 1 ) {
               b.className = "corner";
           } else {
               if( j == 0 || j == brdSize - 1 || i == 0 || i == brdSize - 1 ) {
                   b.className = "fire";
                   b.id = "fire" + ( i + j * brdSize );
               } else {
                   b.className = "atom";
                   b.id = "atom" + ( i + j * brdSize );
               }
               b.addEventListener( "click", 
                   function( e ) {
                       if( e.target.className == "fire" && e.target.innerHTML == " " ) fireBeam( e.target );
                       else if( e.target.className == "atom" ) setAtom( e.target );
                   }, false );
           }
           b.appendChild( document.createTextNode( " " ) );
           brd.appendChild( b );
       }
   }
   board = new Array( brdSize );
   for( var j = 0; j < brdSize; j++ ) {
       board[j] = new Array( brdSize );
       for( i = 0; i < brdSize; i++ ) {
           board[j][i] = {H: 0, T: 0};
       }
   }
   guesses = 0; beamCnt = 1;
   atoms = brdSize == 7 ? 3 : brdSize == 10 ? 4 : 4 + Math.floor( Math.random() * 5 );
   var s = brdSize - 2, i, j;
   for( var k = 0; k < atoms; k++ ) {
       while( true ) {
           i = 1 + Math.floor( Math.random() * s );
           j = 1 + Math.floor( Math.random() * s );
           if( board[i][j].H == 0 ) break;
       }
       board[i][j].H = 1;
   }

} function init() {

   sel = document.createElement( "select");
   sel.options.add( new Option( "5 x 5 [3 atoms]", 7 ) );
   sel.options.add( new Option( "8 x 8 [4 atoms]", 10 ) );
   sel.options.add( new Option( "10 x 10 [4 - 8 atoms]", 12 ) );
   sel.addEventListener( "change", startGame, false );
   document.getElementById( "top" ).appendChild( sel );
   
   check = document.createElement( "button" );
   check.appendChild( document.createTextNode( "Check it!" ) );
   check.className = "hide";
   check.addEventListener( "click", checkIt, false );
   
   again = document.createElement( "button" );
   again.appendChild( document.createTextNode( "Again" ) );
   again.className = "hide";
   again.addEventListener( "click", startGame, false );
   
   para = document.createElement( "p" );
   para.className = "txt";
   var d = document.getElementById( "bot" );
   
   d.appendChild( para );
   d.appendChild( check );
   d.appendChild( again );
   startGame();

} </lang>