SimonHTML5Cam en ChromeExperiments

Posiblemente recordéis, aquellos que ya se han pasado por este blog antes, SimónHTML5Cam. Es un juego del tipo "Simón dice..." que se juega con la webcam. En ésta entrada del blog presentamos el juego.
Ahora se le han añadido unas cuantas mejoras y se ha enviado a la famosa página de Google en la cual se presentan experimentos que hacen uso de las nuevas tecnologías web, hablo de ChromeExperiments.com


Novedades de la versión:

  • Ahora el juego tiene un botón que te permite elegir el momento en el que quieres empezar el juego. Si no pulsas el botón Start, puedes quedarte todo el tiempo que quieras tocando libremente los botones. 
  • Ahora se puede visualizar la última puntuación y la mejor puntuación de la sesión actual.
  • Utiliza el Web Audio API para reproducir los sonidos. Debido a que Google Drive, que es donde se encuentra el juego hospedado, da el error 403 al cargar los archivos de audio, se han usado etiquetas de audio con el src igualado al archivo de audio en base 64. Sin embargo, el código mostrado a continuación utiliza el Web Audio API.
Code:
<!DOCTYPE html>
<html>
    
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>SimonHTML5Cam</title>
        <style>

            body {
                background-color: #550000;
            }

            .hidden {
                visibility: hidden;
            }

            .instructions {
             -webkit-transition: 1s;
             text-align: justify;
             position: absolute;
             right: 0px;
             width: 130px;
             top: 50px;
             color: white;
             padding: 10px;
             text-shadow: 0px 0px 2px white;
             background-color: rgba(0,0,0,0.5);
             border-bottom-left-radius: 10px;
             border-top-left-radius: 10px;
             border-top-width: 1px;
             border-left-color: white;
             border-left-style: solid;
             border-top-color: white;
             border-top-style: solid;
             border-bottom-style: solid;
            }

            .instructions:hover {
             -webkit-transition: 1s;
             -webkit-transition-delay: 2s;
             width: 200px;
            }

        </style>

        <script>

        (function () {

            var canvas, ctx, width, height,
                marginLR, marginUD, video, mirror;
            var motionCanvas, motionCtx, motionWidth, motionHeight, limit;
            var binaryCanvas, binaryCtx;
            var buttons, buttonWidth, buttonHeight, nPixels, marginDetect, timeActive;
            var startButton;
            var audioCtx;
            var state, combination, turn, lastScore, bestScore;
            var startTime;

            window.addEventListener("load", onLoad);
            window.addEventListener("resize", onResize);

            function onLoad()
            {
                var errors = checkCapabilities();
                if (errors != "")
                {
                    alert(errors);
                    return;
                }

                changeState("waiting");

                video = document.createElement("video");
                navigator.getUserMedia(
                    {video: true, audio: false},
                    setWebcamStream,
                    webcamError
                );
                audioCtx = new AudioContext();
                binaryCanvas = document.createElement("canvas");
                binaryCtx = binaryCanvas.getContext("2d");
                motionCanvas = document.createElement("canvas");
                motionCtx = motionCanvas.getContext("2d");
                motionCanvas.width = binaryCanvas.width = motionWidth = 267;
                motionCanvas.height = binaryCanvas.height = motionHeight = 200;
                limit = 50;
                mirror = true;
                canvas = document.getElementById("mainCanvas");
                ctx = canvas.getContext("2d");
                width = canvas.width;
                height = canvas.height;
                marginLR = 50;
                marginUD = 50;
                bestScore = lastScore = 0;
                onResize();

                var img = document.getElementById("simon");
                ctx.drawImage(img, width/2-img.width/2, height/2-img.height/2);

                combination = [];
                buttons = [];
                buttonWidth = 150;
                buttonHeight = 150;
                nPixels = 15;
                marginDetect = 10;
                timeActive = 500;
                startButton = new Button(640-125, 480-225, 100, 75,
                 document.getElementById("start"));
                var azul = new Button(width*0.04, height*0.2,
                    buttonWidth, buttonHeight, document.getElementById("azul"));
                var verde = new Button(width*0.25, height*0.05,
                    buttonWidth, buttonHeight, document.getElementById("verde"));
                var rojo = new Button(width-width*0.25-buttonWidth, height*0.05,
                    buttonWidth, buttonHeight, document.getElementById("rojo"));
                var amarillo = new Button(width-width*0.04-buttonWidth, height*0.2,
                    buttonWidth, buttonHeight, document.getElementById("amarillo"));
                buttons.push(azul);
                buttons.push(verde);
                buttons.push(rojo);
                buttons.push(amarillo);
                loadSound("res/azul.ogg", azul);
                loadSound("res/verde.ogg", verde);
                loadSound("res/rojo.ogg", rojo);
                loadSound("res/amarillo.ogg", amarillo);
            }

            function onResize()
            {
                if (canvas == null)
                    return;

                var w = (window.innerWidth-marginLR*2)/canvas.width;
                var h = (window.innerHeight-marginUD*2)/canvas.height;
                var scale = Math.min(h, w);
                
                canvas.style.width = (canvas.width*scale)+'px';
                canvas.style.height = (canvas.height*scale)+'px';
                canvas.style.position = 'absolute';
                canvas.style.left = '50%';
                canvas.style.top = '50%';
                canvas.style.marginLeft = -(canvas.width*scale)/2+'px';
                canvas.style.marginTop = -(canvas.height*scale)/2+'px';
            }

            function checkCapabilities()
            {
                var errors = "";

                if (!hasVideo())
                    errors += "- HTML5 video element\n";
                if (!hasCanvas())
                    errors += "- Canvas element\n";
                if (!hasGetUserMedia())
                    errors += "- API getUserMedia\n";
                if (!hasURL())
                    errors += "- URL object\n";
                if (!hasWebAudioCtx())
                    errors += "- Web Audio API\n";

                if (errors != "") {
                    return "Your browser doesn't support the "+
                     "following features:\n" + errors;
                }
                return "";
            }

            function hasVideo()
            {
             var elem = document.createElement("video");
             return !!elem;
            }

            function hasCanvas()
            {
                var elem = document.createElement("canvas");
                return !!(elem.getContext && elem.getContext("2d"));
            }

            function hasGetUserMedia()
            {
                navigator.getUserMedia = navigator.getUserMedia ||
                    navigator.webkitGetUserMedia ||
                    navigator.mozGetUserMedia ||
                    navigator.msGetUserMedia;

                return navigator.getUserMedia;
            }

            function hasURL()
            {
                window.URL = window.URL ||
                    window.webkitURL ||
                    window.mozURL ||
                    window.msURL;

                return (window.URL && window.URL.createObjectURL);
            }

            function hasWebAudioCtx()
            {
             window.AudioContext =
           window.AudioContext ||
           window.webkitAudioContext;

             return window.AudioContext;
            }

            function webcamError(e)
            {
                alert("Problems obtaining the webcam stream.");
            }

            function setWebcamStream(stream)
            {
                video.src = window.URL.createObjectURL(stream);
                video.play();
                setInterval(updateWorld, 1000/10);
                startTime = Date.now();
                canvas.style.boxShadow = "0px 0px 10px #FFF";
            }

            function playSound(buffer)
   {
    if (!buffer)
    {
     return;
    }
    var source = audioCtx.createBufferSource();
    source.buffer = buffer;
    source.connect(audioCtx.destination);
    source.start(0);
   }

   function loadSound(audioName, button)
   {
    var request = new XMLHttpRequest();
          request.open("GET", audioName, true);
          request.responseType = "arraybuffer";
          request.addEventListener("load", function ()
          {
           audioCtx.decodeAudioData(
            request.response,
            function (buffer)
            {
             button.setAudio(buffer);
            },
            errorSound
           );
          });
          request.send();
   }

   function errorSound()
         {
          alert("Error loading a sound.");
         }

         function changeState(newState)
         {
          state = newState;
         }

            function updateWorld()
            {
                update();
                paint();
            }

            function update()
            {
             if (Date.now()-startTime <= 2000)
             {
              return;
             }
             if (state == "waiting" && startButton.hasMotion())
             {
              getNotes();
             }
                for (var i=0; i<buttons.length; i++)
                {
                 if (buttons[i].hasMotion())
                 {
                     if (state == "playing")
                     {
                         if (combination[turn] != i)
                         {
                             youLose();
                         }
                         else
                         {
                             if (++turn == combination.length)
                             {
                                 changeState("ok");
                                 setTimeout(getNotes, timeActive*2);
                                 turn++;
                             }
                         }
                     }
                 }
                }
            }

            function paint()
            {
             // Painting webcam
                ctx.save();
                if (mirror)
                {
                    ctx.translate(width, 0);
                    ctx.scale(-1, 1);
                }
                ctx.drawImage(video, width/2-video.videoWidth/2,
                    height/2-video.videoHeight/2);
                ctx.restore();

                // Painting buttons
                for (var i=0; i<buttons.length; i++)
                {
                    buttons[i].paint();
                }

                if (state == "waiting")
                {
                 startButton.paint();
                    ctx.textBaseline = "bottom";
                    ctx.font = "25px san-serif";
                    
                    ctx.lineWidth = 4;
                    ctx.strokeStyle = "#FFF";

                    ctx.strokeText(" Last score: "+lastScore, 0, height-25);
                    ctx.strokeText(" Best score: "+bestScore, 0, height);

                    ctx.fillText(" Last score: "+lastScore, 0, height-25);
                    ctx.fillText(" Best score: "+bestScore, 0, height);
                }
                else if (state == "wrong")
                {
                    ctx.textBaseline = "bottom";
                    ctx.font = "30px san-serif";
                    ctx.lineWidth = 4;
                    ctx.strokeStyle = "#FFF";
                    ctx.strokeText(" Game over! Score: "+(combination.length-1),
                         0, height);
                    ctx.fillStyle = "red";
                    ctx.fillText(" Game over! Score: "+(combination.length-1),
                         0, height);
                    ctx.fillStyle = "#000"
                }
                else if (state == "ok")
                {
                    ctx.textBaseline = "bottom";
                    ctx.font = "30px san-serif";
                    ctx.lineWidth = 4;
                    ctx.strokeStyle = "#FFF";
                    ctx.strokeText(" Good! Score: "+combination.length,0, height);
                    ctx.fillStyle = "green";
                    ctx.fillText(" Good! Score: "+combination.length,0, height);
                    ctx.fillStyle = "#00";
                }

                // Painting in the motion canvas
                var pixels1 = motionCtx.getImageData(0, 0,
                    motionWidth, motionHeight);

                motionCtx.save();
                if (mirror)
                {
                    motionCtx.translate(motionWidth, 0);
                    motionCtx.scale(-1, 1);
                }
                motionCtx.drawImage(video, 0, 0, motionWidth,motionHeight);
                motionCtx.restore();

                var pixels2 = motionCtx.getImageData(0, 0,
                    motionWidth, motionHeight);

                var binaryPixels = getBinaryImage(pixels1, pixels2);
                binaryCtx.putImageData(binaryPixels, 0, 0);
                // Show the binary image of movement
                //ctx.putImageData(binaryCtx.getImageData(0,0,motionWidth, motionHeight),0,0);
            }

            function youLose()
            {
             changeState("wrong");
             lastScore = combination.length-1;
                bestScore = (lastScore > bestScore) ? lastScore : bestScore;
                document.getElementById("fin").play();
                setTimeout(
                 function ()
                    {
                        changeState("waiting");
                        turn = 0;
                        combination = [];
                    },
                    2000
                );
            }

            function getBinaryImage(pixels1, pixels2)
            {
                var data1 = pixels1.data;
                var data2 = pixels2.data;

                for (var i=0; i<data1.length; i+=4)
                {
                    if (Math.abs(data1[i]-data2[i])>limit ||
                        Math.abs(data1[i+1]-data2[i+1])>limit ||
                        Math.abs(data1[i+2]-data2[i+2])>limit)
                    {
                        data1[i] = data1[i+1] = data1[i+2] = 255;
                    }
                    else
                    {
                        data1[i] = data1[i+1] = data1[i+2] = 0;
                    }
                    data1[i+3] = 255;
                }

                return pixels1;
            }

            function getNotes()
            {
                changeState("sequence");
                combination.push(~~(Math.random()*buttons.length));
                timeActive = 500-turn*15;
                timeActive = (timeActive < 100) ? 100: timeActive;
                for (var i=0; i<buttons.length; i++)
                {
                 buttons[i].setActive(false);
                }
                for (var i=0; i<combination.length; i++)
                {
                    getNote(i);
                }
                setTimeout(
                 function ()
                 {
                     changeState("playing");
                     turn = 0;
                 },
                 timeActive*i
                );
                timeActive = 500;
            }

            function getNote(number)
            {
                setTimeout(
                 function ()
                 {
                     if (number-1 >= 0) {
                         buttons[combination[number-1]].setActive(false);
                         buttons[combination[number-1]].paint();
                     }
                     buttons[combination[number]].setActive(true);
                 },
                 timeActive*number
             );
            }

            function Button (x, y, w, h, img)
            {
                this.x = x;
                this.y = y;
                this.w = w;
                this.h = h;
                this.img = img;
                this.active = false;
                this.timeActive = 0;
                this.audio = null;
            }

            Button.prototype = {
                paint: function ()
                {
                    ctx.save();
                    if (this.active)
                    {
                        ctx.globalCompositeOperation = "lighter";
                    }
                    ctx.drawImage(this.img, this.x, this.y, this.w, this.h);
                    ctx.restore();
                },
                hasMotion: function () {

                    if (state == "ok" || state == "wrong" || state == "sequence")
                    {
                        return false;
                    }

                 if (this.active)
                 {
                  if (Date.now()-this.timeActive >= timeActive)
                  {
                   this.setActive(false);
                  }
                 }
                 else
                 {
                     var pixels = binaryCtx.getImageData(
                         motionWidth*this.x/width+marginDetect,
                         motionHeight*this.y/height+marginDetect,
                         motionWidth*this.w/width-marginDetect*2,
                         motionHeight*this.h/height-marginDetect*2);
                     var data = pixels.data;
                     var whitePixels = 0;

                     for (var i=0; whitePixels<nPixels && i<data.length; i+=4)
                     {
                         if (data[i] == 255) {
                             whitePixels++;
                         }
                     }

                     if (whitePixels >= nPixels)
                     {
                         if (!this.active)
                         {
                             this.setActive(true);
                             return true;
                         }
                     }
                 }

                    return false;
                },
                setActive: function (bool)
                {
                 if (bool)
                 {
                     this.timeActive = Date.now();
                     this.active = true;
                     playSound(this.audio);
                    }
                    else
                    {
                     this.active = false;
                    }
                },
                setAudio: function (buffer)
                {
                 this.audio = buffer;
                }
            };

        })();

        </script>
    </head>
    
    <body>
        <noscript>
            Your browser doesn't have activated JavaScript.
        </noscript>
        <audio id="fin" src="res/fin.ogg"></audio>
        <img id="simon" class="hidden" src="res/simon.png"></img>
        <img id="azul" class="hidden" src="res/azul.png"></img>
        <img id="verde" class="hidden" src="res/verde.png"></img>
        <img id="rojo" class="hidden" src="res/rojo.png"></img>
        <img id="amarillo" class="hidden" src="res/amarillo.png"></img>
        <img id="start" class="hidden" src="res/start.png"></img>
        <canvas id="mainCanvas" width="640" height="480">
         Your browser doesn't support HTML5 Canvas element.
        </canvas>
        <div class="instructions">
         This is a "Simon says..." game that you can play with the webcam.<br /><br />
         1.- Move your hand to the "Start" button and remember the sequence of sounds.<br /><br />
         2.- Repeat the sequence by touching the colored buttons in the correct order.<br /><br />
   If you don't press the "Start" button you can use the game as a musical instrument.
        </div>
    </body>
</html>

No hay comentarios :

Publicar un comentario