<commit_before>'use strict';

angular.module('cnedApp').controller('ImagesCtrl', function($scope, $http, $rootScope, $location, $compile) {

    // Zones a découper
    $scope.zones = [];
    // Loader afficher/enlever manipulation
    $scope.loader = false;
    // Image courante dans l'espace de travail
    $scope.currentImage = {};
    // Liste générale des blocks
    $scope.blocks = {
        children: []
    };
    // text océrisé
    $scope.textes = {};
    // paramétre d'affichage de l'éditor
    $scope.showEditor = false;
    // Liste des fichiers a uploader
    $scope.files = [];
    // Garder l'ID du docuument enregistre
    $rootScope.idDocument;
    // Liste des tags
    $scope.listTags = [];
    // Initialisation liste profils
    $scope.listProfils = [];

    /* Ajout nouveaux blocks */
    $scope.toggleMinimized = function(child) {
        child.minimized = !child.minimized;
    };

    $scope.remove = function(child) {
        function walk(target) {
            var children = target.children,
                i;
            if (children) {
                i = children.length;
                while (i--) {
                    if (children[i] === child) {
                        return children.splice(i, 1);
                    } else {
                        walk(children[i]);
                    }
                }
            }
        }
        walk($scope.blocks);
    };

    function traverse(obj, cropedImages) {
        for (var key in obj) {
            if (typeof(obj[key]) == "object") {
                if ($scope.currentImage.source == obj[key].source) {
                    for (var j = 0; j < $scope.cropedImages.length; j++) {
                        obj[key].children.push({
                            text: cropedImages[j].text,
                            source: cropedImages[j].source,
                            children: []
                        });
                    };
                }
                traverse(obj[key], cropedImages);
            }
        }
    }

    function traverseOcrSpeech(obj) {
        for (var key in obj) {
            if (typeof(obj[key]) == "object") {
                if ($scope.currentImage.source == obj[key].source) {
                    obj[key].text = $scope.currentImage.text;
                    obj[key].synthese = $scope.currentImage.synthese;
                    obj[key].tag = $scope.currentImage.tag;
                    break;
                }
                traverseOcrSpeech(obj[key]);
            }
        }
    }

    // $rootScope.bodystyle = "overflow:hidden;";

    $scope.selected = function(x) {
        // Ajouter les dimentions sélectionnés a la table des zones
        $scope.zones.push(x);
        // Enlever la selection
        $rootScope.$emit('releaseCrop');
    };

    // submit crop data
    $scope.sendCrop = function(source) {

        if ($scope.zones.length < 1) {
            alert("pas de zones a découper ... ");
        }

        // initialiser le nombre d'appel du service du découpage a 0
        var callsFinish = 0;
        // Initialiser la table des image découpés
        $scope.cropedImages = [];
        // Manipulation du style du body
        // $scope.bodystyle = "overflow:hidden;";

        // Parcourir les zones pour déoupage
        angular.forEach($scope.zones, function(zone, key) {
            // afficher le loader
            $scope.loader = true;
            // Ajouter l'image de source pour le découpage
            zone.srcImg = source;

            // Appel du service de découpage
            $http.post("/images", {
                DataCrop: zone
            }).success(function(data, status, headers, config) {

                // Créer un objet avec l'image découpée
                var imageTreated = {};
                imageTreated.source = angular.fromJson(data);
                imageTreated.text = '';
                imageTreated.level = Number($scope.currentImage.level + 1);
                imageTreated.children = [];
                $scope.cropedImages.push(imageTreated);

                // incrémenter le nombre d'appel du service de 1
                callsFinish += 1;
                /* Si le nombre d'appel est égale au nombre de zones
                Parcourir le tableau des images découpés est l'ajouter a la liste generale des blocks  */
                if ($scope.zones.length == callsFinish) {

                    // Enlever le loader
                    $scope.loader = false;
                    // Initialiser les zones
                    initialiseZones();

                    // Détecter le parent des blocks et ajouter les images découpés a ce block
                    traverse($scope.blocks, $scope.cropedImages);

                    // for (var i = $scope.blocks.length - 1; i >= 0; i--) {
                    //     if ($scope.blocks[i].source == $scope.currentImage.source) {
                    //         $scope.blocks[i].children = $scope.cropedImages;
                    //         for (var j = 0; j < $scope.cropedImages.length; j++) {
                    //             $scope.blocks.splice(i + j + 1, 0, $scope.cropedImages[j]);
                    //         };
                    //     }
                    // };
                }
            }).error(function(data, status, headers, config) {
                $scope.msg = "ko";
            });
        });

    };

    /* Initialiser la liste des zones */

    function initialiseZones() {
        $scope.zones = [];
    }

    // Appliquer l'océrisation
    $scope.oceriser = function() {

        console.log("in controller ==> ");
        console.log($scope.currentImage);

        // Appel du websevice de l'ocerisation
        if ($scope.currentImage.source) {
            initialiseZones();
            $scope.loader = true;
            $http.post("/oceriser", {
                sourceImage: $scope.currentImage.source
            }).success(function(data, status, headers, config) {
                console.log(data);
                // Ajouter l'objet comportant le text et l'image pour l'affichage sur le workspace
                $scope.textes = {
                    source: $scope.currentImage.source,
                    text: angular.fromJson(data)
                };
                $scope.currentImage.text = angular.fromJson(data);

                // Affichage de l'éditeur
                $scope.showEditor = true;
                $scope.loader = false;
                $scope.msg = "ok";
            }).error(function(data, status, headers, config) {
                $scope.msg = "ko";
            });
        } else {
            alert("Vous devez selectionner un block ... ");
        }

    }

    $scope.modifierTexte = function() {
        // Appel du websevice de l'ocerisation
        if ($scope.currentImage.source) {
            $scope.textes = {
                text: $scope.currentImage.text
            };
            // Affichage de l'éditeur
            $scope.showEditor = true;

        } else {
            alert("Vous devez selectionner un block ... ");
        }
    }

    $scope.textToSpeech = function() {
        // var ocrText = CKEDITOR.instances.editorOcr.document.getBody().getText();
        //
        // console.log(ocrText);
        console.log("currentImage in textToSpeech ==> ");
        var ocrText = $scope.removeAccents($scope.removeHtmlTags($scope.currentImage.text));
        console.log(ocrText);
        // $scope.currentImage.synthese = './files/audio/mp3/audio_0.9142583780921996.mp3';
        console.log($scope.currentImage);
        if ($scope.currentImage.text) {
            if ($scope.currentImage.text.length > 0) {
                $http.post("/texttospeech", {
                    text: $scope.currentImage.text
                }).success(function(data, status, headers, config) {
                    console.log("file of speech text ==> ");
                    console.log(data);
                    $scope.currentImage.synthese = angular.fromJson(data);
                    traverseOcrSpeech($scope.blocks);
                    console.log("synthese finshed ==>  ");
                    console.log($scope.blocks);
                    console.log("ok");
                    return false;
                }).error(function(data, status, headers, config) {
                    console.log("ko");
                });
            } else {
                alert("Pas de texte enregistré pour ce block");
            }
        } else {
            alert("Pas de texte enregistré pour ce block");
        }

    }

    // WYSIWYG Editor Methods
    /* Get OCR and save it */
    $scope.getOcrText = function(argument) {
        // $scope.currentImage.text = htmlToPlaintext(CKEDITOR.instances['editorOcr'].getData());
        $scope.currentImage.text = $scope.removeHtmlTags(CKEDITOR.instances['editorOcr'].getData());
        // console.log($compile(CKEDITOR.instances['editorOcr'].getData()));
        console.log("currentImage ==> ");
        console.log($scope.currentImage);
        traverseOcrSpeech($scope.blocks);

        console.log("ocr finshed ==> ");
        console.log($scope.blocks);
        $scope.textes = {};
        // Affichage de l'éditeur
        $scope.showEditor = false;
    }

    // Get Plain text without html tags

    function htmlToPlaintext(text) {
        return String(text).replace(/<(?:.|\n)*?>/gm, '');
    }

    /* Fonctions de l'upload des fichiers */
    $scope.setFiles = function(element) {
        $scope.$apply(function(scope) {
            // Turn the FileList object into an Array
            for (var i = 0; i < element.files.length; i++) {
                if (element.files[i].type != "image/jpeg" && element.files[i].type != "image/png" && element.files[i].type != "application/pdf") {
                    alert("Le type de fichier rattaché est non autorisé. Merci de rattacher que des fichiers PDF ou des images.");
                    console.log(+element.files[i]);
                } else {
                    $scope.files.push(element.files[i]);
                }
            }
            // $scope.progressVisible = false;
        });
    };

    $scope.uploadFile = function() {
        if ($scope.files.length > 0) {
            var fd = new FormData();
            for (var i in $scope.files) {
                fd.append("uploadedFile", $scope.files[i]);
            }
            var xhr = new XMLHttpRequest();
            // xhr.upload.addEventListener("progress", uploadProgress, false);
            xhr.addEventListener("load", $scope.uploadComplete, false);
            xhr.addEventListener("error", uploadFailed, false);
            // xhr.addEventListener("abort", uploadCanceled, false);
            xhr.open("POST", "/fileupload");
            $scope.progressVisible = true;
            xhr.send(fd);
            $scope.loader = true;
        } else {
            alert("Vous devez choisir un fichier")
        }

    }

    /*function uploadProgress(evt) {
        $scope.$apply(function() {
            if (evt.lengthComputable) {
                $scope.progress = Math.round(evt.loaded * 100 / evt.total);
            } else {
                $scope.progress = 'unable to compute';
            }
        })
    }*/

    $scope.uploadComplete = function(evt) {
        /* This event is raised when the server send back a response */
        $scope.files = [];
        $scope.affectSrcValue(angular.fromJson(evt.target.responseText));
    }

    function uploadFailed(evt) {
        console.log("Erreure survenue lors de l'pload du fichier ")
    }

    /*function uploadCanceled(evt) {
        $scope.$apply(function() {
            $scope.progressVisible = false
        })
        console.log("The upload has been canceled by the user or the browser dropped the connection.")
    }*/

    $scope.affectSrcValue = function(srcs) {
        $rootScope.$emit('distroyJcrop');
        for (var i = 0; i < srcs.length; i++) {
            $scope.blocks.children.push({
                level: 0,
                text: '',
                synthese: '',
                source: srcs[i],
                children: []
            });

        };
        initialiseZones();
        $scope.files = [];
        $scope.loader = false;

        // refresh scope binding : for callbacks of methods not with angularJS
        $scope.$apply();
    }

    // Export Image to workspace
    $scope.workspace = function(image) {
        console.log("in workspace ==> ");
        console.log(image);
        $scope.currentImage = image;
        //$scope.currentImage.level = image.level;
        initialiseZones();
        $scope.textes = {};
        $scope.showEditor = false;
    }

    $scope.showByLevel = function(level) {
        console.log("calling level ==> " + level);
        if (level > 0) {
            return true;
            $scope.$apply();
        } else {
            return false;
            $scope.$apply();
        }
        // return show;
    }

    $scope.permitSaveblocks = function() {
        if ($scope.blocks.children.length < 1) {
            // alert("il n y a pas encore de choses a enregistrer");
            return true;
        } else {
            return false;
        }
    }

    $scope.saveblocks = function() {
        console.log("save blocks saved ==> ");
        console.log($scope.blocks);

        /*var parentBlocks = [];
        for (var i = 0; i < $scope.blocks.length; i++) {
            if ($scope.blocks[i].level == 0) {
                parentBlocks.push($scope.blocks[i]);
            }
        };
        console.log(parentBlocks);
        $http.post("/ajouterDocStructure", {
            blocks: parentBlocks
        }).success(function(data, status, headers, config) {
            $rootScope.idDocument = angular.fromJson(data);
            console.log(data);
            console.log("ok");
        }).error(function(data, status, headers, config) {
            console.log("ko");
        });*/

        $http.post('/ajouterDocStructure', angular.toJson($scope.blocks.children))
            .success(function(data, status, headers, config) {
            $rootScope.idDocument = angular.fromJson(data);
            console.log(data);
            console.log("ok");
        })
            .error(function(data, status, headers, config) {
            console.log("ko");
        });

    }

    $scope.showlocks = function() {
        console.log("show blocks clicked ... ");
        if ($rootScope.idDocument && $rootScope.idDocument.length > 0) {
            $rootScope.profil_id = $scope.profilSelected;
            $location.path("/apercu");
        }
    }

    /* Faire un select sur les tags / les profils */
    // Selection des tags
    $scope.afficherTags = function() {
        $http.get('/readTags')
            .success(function(data) {
            if (data != 'err') {
                $scope.listTags = data;
            }
        });
    };

    $scope.afficherTags();

    // Selection des profils
    $scope.afficherProfils = function() {
        $http.get('/listerProfil')
            .success(function(data) {
            if (data != 'err') {
                $scope.listProfils = data;
            }
        });
    };

    $scope.afficherProfils();

    $scope.updateBlockType = function() {
        $scope.currentImage.tag = $scope.tagSelected;
        traverseOcrSpeech($scope.blocks);
        // Parcour blocks and update with currentImage
    }

    $scope.playSong = function() {
        var audio = document.getElementById("player");
        audio.setAttribute("src", $scope.currentImage.synthese);
        audio.load();
        audio.play();
    }

    // Remplacer les accents pour la synthese vocale
    $scope.removeAccents = function(value) {
        return value.replace(/â/g, 'â')
            .replace(/&agrave/g, 'à')
            .replace(/é/g, 'é')
            .replace(/ê/g, 'ê')
            .replace(/è/g, 'è')
            .replace(/ë/g, 'ë')
            .replace(/î/g, 'î')
            .replace(/ï/g, 'ï')
            .replace(/ô/g, 'ô')
            .replace(/œ/g, 'œ')
            .replace(/û/g, 'û')
            .replace(/ù/g, 'ù')
            .replace(/ü/g, 'ü')
            .replace(/ç/g, 'ç')
            .replace(/</g, '<')
            .replace(/>/g, '>');
    }

    // enlever les tags HTML
    $scope.removeHtmlTags = function(value) {
        // return value.replace(/['"]/g, "");
        return value.replace(/<\/?[^>]+(>|$)/g, "");
    }

});<commit_msg><commit_after>'use strict';

angular.module('cnedApp').controller('ImagesCtrl', function($scope, $http, $rootScope, $location, $compile) {

    // Zones a découper
    $scope.zones = [];
    // Loader afficher/enlever manipulation
    $scope.loader = false;
    // Image courante dans l'espace de travail
    $scope.currentImage = {};
    // Liste générale des blocks
    $scope.blocks = {
        children: []
    };
    // text océrisé
    $scope.textes = {};
    // paramétre d'affichage de l'éditor
    $scope.showEditor = false;
    // Liste des fichiers a uploader
    $scope.files = [];
    // Garder l'ID du docuument enregistre
    $rootScope.idDocument;
    // Liste des tags
    $scope.listTags = [];
    // Initialisation liste profils
    $scope.listProfils = [];

    /* Ajout nouveaux blocks */
    $scope.toggleMinimized = function(child) {
        child.minimized = !child.minimized;
    };

    $scope.remove = function(child) {
        function walk(target) {
            var children = target.children,
                i;
            if (children) {
                i = children.length;
                while (i--) {
                    if (children[i] === child) {
                        return children.splice(i, 1);
                    } else {
                        walk(children[i]);
                    }
                }
            }
        }
        walk($scope.blocks);
    };

    function traverse(obj, cropedImages) {
        for (var key in obj) {
            if (typeof(obj[key]) == "object") {
                if ($scope.currentImage.source == obj[key].source) {
                    for (var j = 0; j < $scope.cropedImages.length; j++) {
                        obj[key].children.push({
                            text: cropedImages[j].text,
                            source: cropedImages[j].source,
                            children: []
                        });
                    };
                }
                traverse(obj[key], cropedImages);
            }
        }
    }

    function traverseOcrSpeech(obj) {
        for (var key in obj) {
            if (typeof(obj[key]) == "object") {
                if ($scope.currentImage.source == obj[key].source) {
                    obj[key].text = $scope.currentImage.text;
                    obj[key].synthese = $scope.currentImage.synthese;
                    obj[key].tag = $scope.currentImage.tag;
                    break;
                }
                traverseOcrSpeech(obj[key]);
            }
        }
    }

    // $rootScope.bodystyle = "overflow:hidden;";

    $scope.selected = function(x) {
        // Ajouter les dimentions sélectionnés a la table des zones
        $scope.zones.push(x);
        // Enlever la selection
        $rootScope.$emit('releaseCrop');
    };

    // submit crop data
    $scope.sendCrop = function(source) {

        if ($scope.zones.length < 1) {
            alert("pas de zones a découper ... ");
        }

        // initialiser le nombre d'appel du service du découpage a 0
        var callsFinish = 0;
        // Initialiser la table des image découpés
        $scope.cropedImages = [];
        // Manipulation du style du body
        // $scope.bodystyle = "overflow:hidden;";

        // Parcourir les zones pour déoupage
        angular.forEach($scope.zones, function(zone, key) {
            // afficher le loader
            $scope.loader = true;
            // Ajouter l'image de source pour le découpage
            zone.srcImg = source;

            // Appel du service de découpage
            $http.post("/images", {
                DataCrop: zone
            }).success(function(data, status, headers, config) {

                // Créer un objet avec l'image découpée
                var imageTreated = {};
                imageTreated.source = angular.fromJson(data);
                imageTreated.text = '';
                imageTreated.level = Number($scope.currentImage.level + 1);
                imageTreated.children = [];
                $scope.cropedImages.push(imageTreated);

                // incrémenter le nombre d'appel du service de 1
                callsFinish += 1;
                /* Si le nombre d'appel est égale au nombre de zones
                Parcourir le tableau des images découpés est l'ajouter a la liste generale des blocks  */
                if ($scope.zones.length == callsFinish) {

                    // Enlever le loader
                    $scope.loader = false;
                    // Initialiser les zones
                    initialiseZones();

                    // Détecter le parent des blocks et ajouter les images découpés a ce block
                    traverse($scope.blocks, $scope.cropedImages);

                    // for (var i = $scope.blocks.length - 1; i >= 0; i--) {
                    //     if ($scope.blocks[i].source == $scope.currentImage.source) {
                    //         $scope.blocks[i].children = $scope.cropedImages;
                    //         for (var j = 0; j < $scope.cropedImages.length; j++) {
                    //             $scope.blocks.splice(i + j + 1, 0, $scope.cropedImages[j]);
                    //         };
                    //     }
                    // };
                }
            }).error(function(data, status, headers, config) {
                $scope.msg = "ko";
            });
        });

    };

    /* Initialiser la liste des zones */

    function initialiseZones() {
        $scope.zones = [];
    }

    // Appliquer l'océrisation
    $scope.oceriser = function() {

        console.log("in controller ==> ");
        console.log($scope.currentImage);

        // Appel du websevice de l'ocerisation
        if ($scope.currentImage.source) {
            initialiseZones();
            $scope.loader = true;
            $http.post("/oceriser", {
                sourceImage: $scope.currentImage.source
            }).success(function(data, status, headers, config) {
                console.log(data);
                // Ajouter l'objet comportant le text et l'image pour l'affichage sur le workspace
                $scope.textes = {
                    source: $scope.currentImage.source,
                    text: angular.fromJson(data)
                };
                $scope.currentImage.text = angular.fromJson(data);

                // Affichage de l'éditeur
                $scope.showEditor = true;
                $scope.loader = false;
                $scope.msg = "ok";
            }).error(function(data, status, headers, config) {
                $scope.msg = "ko";
            });
        } else {
            alert("Vous devez selectionner un block ... ");
        }

    }

    $scope.modifierTexte = function() {
        // Appel du websevice de l'ocerisation
        if ($scope.currentImage.source) {
            $scope.textes = {
                text: $scope.currentImage.text
            };
            // Affichage de l'éditeur
            $scope.showEditor = true;

        } else {
            alert("Vous devez selectionner un block ... ");
        }
    }

    $scope.textToSpeech = function() {
        // var ocrText = CKEDITOR.instances.editorOcr.document.getBody().getText();
        //
        // console.log(ocrText);
        console.log("currentImage in textToSpeech ==> ");
        var ocrText = $scope.removeAccents($scope.removeHtmlTags($scope.currentImage.text));
        $scope.currentImage.text = ocrText;
        console.log(ocrText);
        // $scope.currentImage.synthese = './files/audio/mp3/audio_0.9142583780921996.mp3';
        console.log($scope.currentImage);
        if ($scope.currentImage.text) {
            if ($scope.currentImage.text.length > 0) {
                $http.post("/texttospeech", {
                    text: $scope.currentImage.text
                }).success(function(data, status, headers, config) {
                    console.log("file of speech text ==> ");
                    console.log(data);
                    $scope.currentImage.synthese = angular.fromJson(data);
                    traverseOcrSpeech($scope.blocks);
                    console.log("synthese finshed ==>  ");
                    console.log($scope.blocks);
                    console.log("ok");
                    return false;
                }).error(function(data, status, headers, config) {
                    console.log("ko");
                });
            } else {
                alert("Pas de texte enregistré pour ce block");
            }
        } else {
            alert("Pas de texte enregistré pour ce block");
        }

    }

    // WYSIWYG Editor Methods
    /* Get OCR and save it */
    $scope.getOcrText = function(argument) {
        // $scope.currentImage.text = htmlToPlaintext(CKEDITOR.instances['editorOcr'].getData());
        $scope.currentImage.text = $scope.removeHtmlTags(CKEDITOR.instances['editorOcr'].getData());
        // console.log($compile(CKEDITOR.instances['editorOcr'].getData()));
        console.log("currentImage ==> ");
        console.log($scope.currentImage);
        traverseOcrSpeech($scope.blocks);

        console.log("ocr finshed ==> ");
        console.log($scope.blocks);
        $scope.textes = {};
        // Affichage de l'éditeur
        $scope.showEditor = false;
    }

    // Get Plain text without html tags

    function htmlToPlaintext(text) {
        return String(text).replace(/<(?:.|\n)*?>/gm, '');
    }

    /* Fonctions de l'upload des fichiers */
    $scope.setFiles = function(element) {
        $scope.$apply(function(scope) {
            // Turn the FileList object into an Array
            for (var i = 0; i < element.files.length; i++) {
                if (element.files[i].type != "image/jpeg" && element.files[i].type != "image/png" && element.files[i].type != "application/pdf") {
                    alert("Le type de fichier rattaché est non autorisé. Merci de rattacher que des fichiers PDF ou des images.");
                    console.log(+element.files[i]);
                } else {
                    $scope.files.push(element.files[i]);
                }
            }
            // $scope.progressVisible = false;
        });
    };

    $scope.uploadFile = function() {
        if ($scope.files.length > 0) {
            var fd = new FormData();
            for (var i in $scope.files) {
                fd.append("uploadedFile", $scope.files[i]);
            }
            var xhr = new XMLHttpRequest();
            // xhr.upload.addEventListener("progress", uploadProgress, false);
            xhr.addEventListener("load", $scope.uploadComplete, false);
            xhr.addEventListener("error", uploadFailed, false);
            // xhr.addEventListener("abort", uploadCanceled, false);
            xhr.open("POST", "/fileupload");
            $scope.progressVisible = true;
            xhr.send(fd);
            $scope.loader = true;
        } else {
            alert("Vous devez choisir un fichier")
        }

    }

    /*function uploadProgress(evt) {
        $scope.$apply(function() {
            if (evt.lengthComputable) {
                $scope.progress = Math.round(evt.loaded * 100 / evt.total);
            } else {
                $scope.progress = 'unable to compute';
            }
        })
    }*/

    $scope.uploadComplete = function(evt) {
        /* This event is raised when the server send back a response */
        $scope.files = [];
        $scope.affectSrcValue(angular.fromJson(evt.target.responseText));
    }

    function uploadFailed(evt) {
        console.log("Erreure survenue lors de l'pload du fichier ")
    }

    /*function uploadCanceled(evt) {
        $scope.$apply(function() {
            $scope.progressVisible = false
        })
        console.log("The upload has been canceled by the user or the browser dropped the connection.")
    }*/

    $scope.affectSrcValue = function(srcs) {
        $rootScope.$emit('distroyJcrop');
        for (var i = 0; i < srcs.length; i++) {
            $scope.blocks.children.push({
                level: 0,
                text: '',
                synthese: '',
                source: srcs[i],
                children: []
            });

        };
        initialiseZones();
        $scope.files = [];
        $scope.loader = false;

        // refresh scope binding : for callbacks of methods not with angularJS
        $scope.$apply();
    }

    // Export Image to workspace
    $scope.workspace = function(image) {
        console.log("in workspace ==> ");
        console.log(image);
        $scope.currentImage = image;
        //$scope.currentImage.level = image.level;
        initialiseZones();
        $scope.textes = {};
        $scope.showEditor = false;
    }

    $scope.showByLevel = function(level) {
        console.log("calling level ==> " + level);
        if (level > 0) {
            return true;
            $scope.$apply();
        } else {
            return false;
            $scope.$apply();
        }
        // return show;
    }

    $scope.permitSaveblocks = function() {
        if ($scope.blocks.children.length < 1) {
            // alert("il n y a pas encore de choses a enregistrer");
            return true;
        } else {
            return false;
        }
    }

    $scope.saveblocks = function() {
        console.log("save blocks saved ==> ");
        console.log($scope.blocks);

        /*var parentBlocks = [];
        for (var i = 0; i < $scope.blocks.length; i++) {
            if ($scope.blocks[i].level == 0) {
                parentBlocks.push($scope.blocks[i]);
            }
        };
        console.log(parentBlocks);
        $http.post("/ajouterDocStructure", {
            blocks: parentBlocks
        }).success(function(data, status, headers, config) {
            $rootScope.idDocument = angular.fromJson(data);
            console.log(data);
            console.log("ok");
        }).error(function(data, status, headers, config) {
            console.log("ko");
        });*/

        $http.post('/ajouterDocStructure', angular.toJson($scope.blocks.children))
            .success(function(data, status, headers, config) {
            $rootScope.idDocument = angular.fromJson(data);
            console.log(data);
            console.log("ok");
        })
            .error(function(data, status, headers, config) {
            console.log("ko");
        });

    }

    $scope.showlocks = function() {
        console.log("show blocks clicked ... ");
        if ($rootScope.idDocument && $rootScope.idDocument.length > 0) {
            $rootScope.profil_id = $scope.profilSelected;
            $location.path("/apercu");
        }
    }

    /* Faire un select sur les tags / les profils */
    // Selection des tags
    $scope.afficherTags = function() {
        $http.get('/readTags')
            .success(function(data) {
            if (data != 'err') {
                $scope.listTags = data;
            }
        });
    };

    $scope.afficherTags();

    // Selection des profils
    $scope.afficherProfils = function() {
        $http.get('/listerProfil')
            .success(function(data) {
            if (data != 'err') {
                $scope.listProfils = data;
            }
        });
    };

    $scope.afficherProfils();

    $scope.updateBlockType = function() {
        $scope.currentImage.tag = $scope.tagSelected;
        traverseOcrSpeech($scope.blocks);
        // Parcour blocks and update with currentImage
    }

    $scope.playSong = function() {
        var audio = document.getElementById("player");
        audio.setAttribute("src", $scope.currentImage.synthese);
        audio.load();
        audio.play();
    }

    // Remplacer les accents pour la synthese vocale
    $scope.removeAccents = function(value) {
        return value.replace(/â/g, 'â')
            .replace(/&agrave/g, 'à')
            .replace(/é/g, 'é')
            .replace(/ê/g, 'ê')
            .replace(/è/g, 'è')
            .replace(/ë/g, 'ë')
            .replace(/î/g, 'î')
            .replace(/ï/g, 'ï')
            .replace(/ô/g, 'ô')
            .replace(/œ/g, 'œ')
            .replace(/û/g, 'û')
            .replace(/ù/g, 'ù')
            .replace(/ü/g, 'ü')
            .replace(/ç/g, 'ç')
            .replace(/</g, '<')
            .replace(/>/g, '>');
    }

    // enlever les tags HTML
    $scope.removeHtmlTags = function(value) {
        // return value.replace(/['"]/g, "");
        return value.replace(/<\/?[^>]+(>|$)/g, "");
    }

});<|endoftext|>


<commit_before>'use strict';

const log = require('util').log;
const path = require('path');
const fse = require('fs-extra');
const spawn = require('child_process').spawn;

const utils = require('../utils.js');

let nodejs = {

    /**
     * Function that checks for git environment variables and clones repository
     * @param  {Object}   options An object that contains params passed to the function
     * @param  {Function} cb      Callback function
     *
     */
    init(options, cb) {

        if (options.nodejs && options.nodejs.git && options.nodejs.git.owner && options.nodejs.git.repo) {
            if (!process.env.NODE_ENV || process.env.NODE_ENV !== 'production') {
                log('WARNING: Environment variable NODE_ENV is not set to "production". This is not recommended in production environments!');
            }

            let repoDirPath = path.join(options.paths.service.path, options.nodejs.git.repo);
            fse.ensureDir(repoDirPath, (error) => {
                if (error) {
                    log(`An error occured while creating ${repoDirPath} ...`);
                    throw new Error(error);
                }

                let cloneOptions = {
                    repo: {
                        git: options.nodejs.git
                    },
                    clonePath: repoDirPath
                };
                utils.clone(cloneOptions, (error) => {
                    if (error) throw new Error(error);

                    options.repoDirPath = repoDirPath;
                    return nodejs.installDeps(options, cb);
                });
            });
        }
        else {
            log(`Missing required git information, found git owner: [${options.nodejs.git.owner}], git repo: [${options.nodejs.git.repo}], exiting ...`);
            return cb();
        }
    },

    /**
     * Function that installs dependencies for a nodejs service
     * @param  {Object}   options An object that contains params passed to the function
     * @param  {Function} cb      Callback function
     *
     */
    installDeps(options, cb) {
        log('Installing service dependencies ...');
        const npm = spawn('npm', [ 'install' ], { stdio: 'inherit', cwd: options.repoDirPath });

        npm.on('data', (data) => {
            console.log (data.toString());
        });

        npm.on('close', (code) => {
            log(`npm install process exited with code: ${code}`);
            return nodejs.run(options, cb);
        });

        npm.on('error', (error) => {
            log('An error occured while installing dependencies');
            throw new Error(error);
        });
    },

    /**
     * Function that runs a nodejs service
     * @param  {Object}   options An object that contains params passed to the function
     * @param  {Function} cb      Callback function
     *
     */
    run(options, cb) {
        log('Running service ...');
        let serviceRun = path.join(options.repoDirPath, '/') + options.nodejs.main;
        let nodeParams = ((options.nodejs.memory) ? '--max_old_space_size=' + options.nodejs.memory : '');

        let runParams = [];
        if (nodeParams) runParams.push(nodeParams);
        runParams.push(serviceRun);

        const node = spawn('node', runParams, { stdio: 'inherit', cwd: options.repoDirPath });

        node.on('data', (data) => {
            console.log (data.toString());
        });

        node.on('close', (code) => {
            log(`node process exited with code: ${code}`);
            return cb();
        });

        node.on('error', (error) => {
            log('An error occured while installing dependencies');
            throw new Error(error);
        });
    }

};

module.exports = {
    deploy: nodejs.init
};
<commit_msg>Removed cwd option from command that runs nodejs services<commit_after>'use strict';

const log = require('util').log;
const path = require('path');
const fse = require('fs-extra');
const spawn = require('child_process').spawn;

const utils = require('../utils.js');

let nodejs = {

    /**
     * Function that checks for git environment variables and clones repository
     * @param  {Object}   options An object that contains params passed to the function
     * @param  {Function} cb      Callback function
     *
     */
    init(options, cb) {

        if (options.nodejs && options.nodejs.git && options.nodejs.git.owner && options.nodejs.git.repo) {
            if (!process.env.NODE_ENV || process.env.NODE_ENV !== 'production') {
                log('WARNING: Environment variable NODE_ENV is not set to "production". This is not recommended in production environments!');
            }

            let repoDirPath = path.join(options.paths.service.path, options.nodejs.git.repo);
            fse.ensureDir(repoDirPath, (error) => {
                if (error) {
                    log(`An error occured while creating ${repoDirPath} ...`);
                    throw new Error(error);
                }

                let cloneOptions = {
                    repo: {
                        git: options.nodejs.git
                    },
                    clonePath: repoDirPath
                };
                utils.clone(cloneOptions, (error) => {
                    if (error) throw new Error(error);

                    options.repoDirPath = repoDirPath;
                    return nodejs.installDeps(options, cb);
                });
            });
        }
        else {
            log(`Missing required git information, found git owner: [${options.nodejs.git.owner}], git repo: [${options.nodejs.git.repo}], exiting ...`);
            return cb();
        }
    },

    /**
     * Function that installs dependencies for a nodejs service
     * @param  {Object}   options An object that contains params passed to the function
     * @param  {Function} cb      Callback function
     *
     */
    installDeps(options, cb) {
        log('Installing service dependencies ...');
        const npm = spawn('npm', [ 'install' ], { stdio: 'inherit', cwd: options.repoDirPath });

        npm.on('data', (data) => {
            console.log (data.toString());
        });

        npm.on('close', (code) => {
            log(`npm install process exited with code: ${code}`);
            return nodejs.run(options, cb);
        });

        npm.on('error', (error) => {
            log('An error occured while installing dependencies');
            throw new Error(error);
        });
    },

    /**
     * Function that runs a nodejs service
     * @param  {Object}   options An object that contains params passed to the function
     * @param  {Function} cb      Callback function
     *
     */
    run(options, cb) {
        log('Running service ...');
        let serviceRun = path.join(options.repoDirPath, '/') + options.nodejs.main;
        let nodeParams = ((options.nodejs.memory) ? '--max_old_space_size=' + options.nodejs.memory : '');

        let runParams = [];
        if (nodeParams) runParams.push(nodeParams);
        runParams.push(serviceRun);

        const node = spawn('node', runParams, { stdio: 'inherit' });

        node.on('data', (data) => {
            console.log (data.toString());
        });

        node.on('close', (code) => {
            log(`node process exited with code: ${code}`);
            return cb();
        });

        node.on('error', (error) => {
            log('An error occured while installing dependencies');
            throw new Error(error);
        });
    }

};

module.exports = {
    deploy: nodejs.init
};
<|endoftext|>


<commit_before>var DeckAround = React.createClass({
  loadGameStateFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  onJoin: function(player) {
    $.ajax({
      url: 'join',
      type: 'POST',
      dataType: 'json',
      data: {name: player},
      success: function(game) {
        this.setState({
          game: game,
          player: player
        });
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('join', status, err.toString());
      }.bind(this)
    });
  },
  onDeal: function() {
    $.ajax({
      url: 'deal',
      type: 'POST',
      dataType: 'json',
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('deal', status, err.toString());
      }.bind(this)
    });
  },
  onPrompt: function(prompt) {
    $.ajax({
      url: 'prompt',
      type: 'POST',
      dataType: 'json',
      data: {prompt: prompt},
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('prompt', status, err.toString());
      }.bind(this)
    });
  },
  onDefine: function(definition) {
    console.log(this.state.player, "submitted", definition);
    $.ajax({
      url: 'define',
      type: 'POST',
      dataType: 'json',
      data: {definition: definition, name: this.state.player},
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('prompt', status, err.toString());
      }.bind(this)
    });
  },
  onStartVoting: function() {
    $.ajax({
      url: 'move-to-vote',
      type: 'POST',
      dataType: 'json',
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('move-to-vote', status, err.toString());
      }.bind(this)
    });
  },
  onVote: function(voter, votee) {
    $.ajax({
      url: 'vote',
      type: 'POST',
      dataType: 'json',
      data: {voter: voter, votee: votee},
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('vote', status, err.toString());
      }.bind(this)
    });
  },
  onEndVoting: function() {
    $.ajax({
      url: 'tally-votes',
      type: 'POST',
      dataType: 'json',
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('tally-votes', status, err.toString());
      }.bind(this)
    });
  },
  onReset: function() {
    $.ajax({
      url: 'reset',
      type: 'POST',
      dataType: 'json',
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('reset', status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {game: {phase: "NotConnected"}};
  },
  componentDidMount: function() {
    this.loadGameStateFromServer();
    setInterval(this.loadGameStateFromServer, this.props.pollInterval);
  },
  render: function() {
    return {
      "NotConnected": <NotConnected />,
      "WaitingForPlayers": <WaitingForPlayers
        players={this.state.game.players} player={this.state.player}
        onJoin={this.onJoin} onDeal={this.onDeal} />,
      "Dealing": <Dealing dealer={this.state.game.dealer}
        player={this.state.player} onPrompt={this.onPrompt} />,
      "Defining": <Defining
        dealer={this.state.game.roundInProgress &&
          this.state.game.roundInProgress.dealer}
        player={this.state.player}
        definitions={this.state.game.roundInProgress &&
          this.state.game.roundInProgress.definitions}
        onDefine={this.onDefine} onStartVoting={this.onStartVoting} />,
      "Voting": <Voting player={this.state.player}
        dealer={this.state.game.roundInProgress &&
          this.state.game.roundInProgress.dealer}
        definitions={this.state.game.roundInProgress &&
          this.state.game.roundInProgress.definitions} onVote={this.onVote}
          onEndVoting={this.onEndVoting} />,
      "EndOfRound": <EndOfRound player={this.state.player}
        players={this.state.game.game && this.state.game.game.players}
        rounds={this.state.game.game && this.state.game.game.rounds}
        onDeal={this.onDeal} />,
      "Over": <Over player={this.state.player}
        players={this.state.game.game && this.state.game.game.players}
        rounds={this.state.game.game && this.state.game.game.rounds}
        onReset={this.onReset} />
    }[this.state.game.phase] ||
unhandled game phase: {this.state.game.phase}
;
  }
});

var NotConnected = React.createClass({
  render: function() {
    return (

        Not connected to the server, trying to connect...

    );
  }
});

var WaitingForPlayers = React.createClass({
  deal: function(e) {
    e.preventDefault();
    this.props.onDeal();
  },
  render: function() {
    var playerList = this.props.players.map(function(player) {
      return (

          {player}

      );
    });
    var join = <JoinForm onJoin={this.props.onJoin} />;
    var deal = (
      <form onSubmit={this.deal}>
        <input type="submit" value="Deal" />
      </form>
    );
    return (

Deck Around

        {-1 < this.props.players.indexOf(this.props.player) ? '' : join}
        {this.props.players.length} Players playing.
        {playerList}
        {3 <= this.props.players.length && this.props.players[0] == this.props.player ? deal : ''}

    );
  }
});

var JoinForm = React.createClass({
  joinGame: function(e) {
    e.preventDefault();
    var name = this.refs.name.getDOMNode().value.trim();
    this.props.onJoin(name);
  },
  render: function() {
    return (
      <form className="joinForm" onSubmit={this.joinGame}>
        <input type="text" placeholder="Your name" ref="name" />
        <input type="submit" value="Join" />
      </form>
    );
  }
});

var Dealing = React.createClass({
  render: function() {
    return this.props.dealer == this.props.player ? (

Draw a card

        <PromptForm onPrompt={this.props.onPrompt} />

      ) : (

Hold tight - {this.props.dealer} is drawing a card.

    );
  }
});

var PromptForm = React.createClass({
  statePrompt: function(e) {
    e.preventDefault();
    var word = this.refs.word.getDOMNode().value.trim();
    this.props.onPrompt(word);
  },
  render: function() {
    return (
      <form className="promptForm" onSubmit={this.statePrompt}>
        <input type="text" placeholder="The word to define" ref="word" />
        <input type="submit" value="Share" />
      </form>
    );
  }
});

var Defining = React.createClass({
  render: function() {
    var playerIsTheDealer = this.props.dealer == this.props.player;
    var theDealerDefinitionExists = -1 < this.props.definitions.map(
        function(def) {
      return def.author;
    }).indexOf(this.props.dealer);
    var thereAreAtLeastThreeDefinitions = 3 <= this.props.definitions.length;
    return (

        <DefiningForm onDefine={this.props.onDefine} />
        {playerIsTheDealer && theDealerDefinitionExists &&
          thereAreAtLeastThreeDefinitions ? <StartVoting
          onStartVoting={this.props.onStartVoting} /> : ''}

    );
  }
});

var DefiningForm = React.createClass({
  onDefine: function(e) {
    e.preventDefault();
    var definition = this.refs.definition.getDOMNode().value.trim();
    this.props.onDefine(definition);
  },
  render: function() {
    return (
      <form className="defineForm" onSubmit={this.onDefine}>
        <textarea placeholder="Write a definition" ref="definition"></textarea>
        <input type="submit" value="Submit" />
      </form>
    );
  }
});

var StartVoting = React.createClass({
  onStartVoting: function(e) {
    e.preventDefault();
    this.props.onStartVoting();
  },
  render: function() {
    return (
      <form className="startVotingForm" onSubmit={this.onStartVoting}>
        <input type="submit" value="Begin voting" />
      </form>
    );
  }
});

var Voting = React.createClass({
  onEndVoting: function(e) {
    e.preventDefault();
    this.props.onEndVoting();
  },
  render: function() {
    var amDealer = this.props.player == this.props.dealer;
    var definitions = this.props.definitions.filter(function(definition) {
      return amDealer || definition.author != this.props.player;
    }.bind(this));
    if (amDealer) {
      definitionElements = definitions.map(function(definition) {
        return
{definition.definition}
;
      });
      return (

          {definitionElements}
          <button onClick={this.onEndVoting}>End Voting</button>

      );
    };
    return <VoteForm player={this.props.player} definitions={definitions}
      onVote={this.props.onVote} />;
  }
});

var VoteForm = React.createClass({
  onVote: function(votee) {
    return function(e) {
      e.preventDefault();
      this.props.onVote(this.props.player, votee);
    }.bind(this);
  },
  render: function() {
    var definitions = this.props.definitions.map(function(definition) {
      return <input type="button" onClick={this.onVote(definition.author)}
        value={definition.definition} />;
    }.bind(this));
    return (

        {definitions}

    );
  }
});

var EndOfRound = React.createClass({
  onDeal: function(e) {
    e.preventDefault();
    this.props.onDeal();
  },
  render: function() {
    var lastRound = this.props.rounds[this.props.rounds.length - 1];
    var scores = endOfRoundSummary(this.props.players, this.props.rounds);
    return (

        {renderRoundSummary(scores)}
        {this.props.player == lastRound.dealer ?
          <input type="button" onClick={this.onDeal}
            value="Start next round" /> : ''}

    );
  }
});

var Over = React.createClass({
  onReset: function(e) {
    e.preventDefault();
    this.props.onReset();
  },
  render: function() {
    var lastRound = this.props.rounds[this.props.rounds.length - 1];
    var scores = endOfRoundSummary(this.props.players, this.props.rounds);
    return (

        {renderRoundSummary(scores)}
        <input type="button" onClick={this.onReset} value="Start new game" />

    );
  }
});

function playerScoreInRound(player, round) {
  if (round.dealer == player) {
    return 0;
  }
  var timesVotedFor = round.votes.filter(function(vote) {
    return vote.votee == player;
  }).length;
  var voteForRound = round.votes.filter(function(vote) {
    return vote.voter == player;
  })[0];
  var votedCorrectly = !!(voteForRound && voteForRound.votee == round.dealer);
  return 2 * timesVotedFor + +votedCorrectly;
}

function endOfRoundSummary(players, rounds) {
  var lastRound = rounds[rounds.length - 1];
  var scores = {};
  players.forEach(function(player) {
    scores[player] = {};
  });
  lastRound.definitions.forEach(function(definition) {
    scores[definition.author].definition = definition.definition;
  });
  players.forEach(function(player) {
    scores[player].roundScore = playerScoreInRound(player, lastRound);
    scores[player].totalScore = rounds.map(function(round) {
      return playerScoreInRound(player, round);
    }).reduce(function(acc, cur) {
      return acc + cur;
    });
  });
  return scores;
}

function renderRoundSummary(summary) {
  var playerRows = Object.keys(summary).map(function(player) {
    score = summary[player];
    return (

        {player}
        {score.roundScore}
        {score.totalScore}
        {score.definition}

    );
  });
  return (

        Player
        Round
        Total
        Definition

      {playerRows}

  );
}

React.render(
  <DeckAround url="status" pollInterval={1000}/>,
  document.getElementById('content')
);
<commit_msg>Show prompt when defining.<commit_after>var DeckAround = React.createClass({
  loadGameStateFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  onJoin: function(player) {
    $.ajax({
      url: 'join',
      type: 'POST',
      dataType: 'json',
      data: {name: player},
      success: function(game) {
        this.setState({
          game: game,
          player: player
        });
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('join', status, err.toString());
      }.bind(this)
    });
  },
  onDeal: function() {
    $.ajax({
      url: 'deal',
      type: 'POST',
      dataType: 'json',
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('deal', status, err.toString());
      }.bind(this)
    });
  },
  onPrompt: function(prompt) {
    $.ajax({
      url: 'prompt',
      type: 'POST',
      dataType: 'json',
      data: {prompt: prompt},
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('prompt', status, err.toString());
      }.bind(this)
    });
  },
  onDefine: function(definition) {
    console.log(this.state.player, "submitted", definition);
    $.ajax({
      url: 'define',
      type: 'POST',
      dataType: 'json',
      data: {definition: definition, name: this.state.player},
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('prompt', status, err.toString());
      }.bind(this)
    });
  },
  onStartVoting: function() {
    $.ajax({
      url: 'move-to-vote',
      type: 'POST',
      dataType: 'json',
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('move-to-vote', status, err.toString());
      }.bind(this)
    });
  },
  onVote: function(voter, votee) {
    $.ajax({
      url: 'vote',
      type: 'POST',
      dataType: 'json',
      data: {voter: voter, votee: votee},
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('vote', status, err.toString());
      }.bind(this)
    });
  },
  onEndVoting: function() {
    $.ajax({
      url: 'tally-votes',
      type: 'POST',
      dataType: 'json',
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('tally-votes', status, err.toString());
      }.bind(this)
    });
  },
  onReset: function() {
    $.ajax({
      url: 'reset',
      type: 'POST',
      dataType: 'json',
      success: function(game) {
        this.setState({game: game});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('reset', status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {game: {phase: "NotConnected"}};
  },
  componentDidMount: function() {
    this.loadGameStateFromServer();
    setInterval(this.loadGameStateFromServer, this.props.pollInterval);
  },
  render: function() {
    return {
      "NotConnected": <NotConnected />,
      "WaitingForPlayers": <WaitingForPlayers
        players={this.state.game.players} player={this.state.player}
        onJoin={this.onJoin} onDeal={this.onDeal} />,
      "Dealing": <Dealing dealer={this.state.game.dealer}
        player={this.state.player} onPrompt={this.onPrompt} />,
      "Defining": <Defining
        prompt={this.state.game.roundInProgress &&
          this.state.game.roundInProgress.prompt}
        dealer={this.state.game.roundInProgress &&
          this.state.game.roundInProgress.dealer}
        player={this.state.player}
        definitions={this.state.game.roundInProgress &&
          this.state.game.roundInProgress.definitions}
        onDefine={this.onDefine} onStartVoting={this.onStartVoting} />,
      "Voting": <Voting player={this.state.player}
        dealer={this.state.game.roundInProgress &&
          this.state.game.roundInProgress.dealer}
        definitions={this.state.game.roundInProgress &&
          this.state.game.roundInProgress.definitions} onVote={this.onVote}
          onEndVoting={this.onEndVoting} />,
      "EndOfRound": <EndOfRound player={this.state.player}
        players={this.state.game.game && this.state.game.game.players}
        rounds={this.state.game.game && this.state.game.game.rounds}
        onDeal={this.onDeal} />,
      "Over": <Over player={this.state.player}
        players={this.state.game.game && this.state.game.game.players}
        rounds={this.state.game.game && this.state.game.game.rounds}
        onReset={this.onReset} />
    }[this.state.game.phase] ||
unhandled game phase: {this.state.game.phase}
;
  }
});

var NotConnected = React.createClass({
  render: function() {
    return (

        Not connected to the server, trying to connect...

    );
  }
});

var WaitingForPlayers = React.createClass({
  deal: function(e) {
    e.preventDefault();
    this.props.onDeal();
  },
  render: function() {
    var playerList = this.props.players.map(function(player) {
      return (

          {player}

      );
    });
    var join = <JoinForm onJoin={this.props.onJoin} />;
    var deal = (
      <form onSubmit={this.deal}>
        <input type="submit" value="Deal" />
      </form>
    );
    return (

Deck Around

        {-1 < this.props.players.indexOf(this.props.player) ? '' : join}
        {this.props.players.length} Players playing.
        {playerList}
        {3 <= this.props.players.length && this.props.players[0] == this.props.player ? deal : ''}

    );
  }
});

var JoinForm = React.createClass({
  joinGame: function(e) {
    e.preventDefault();
    var name = this.refs.name.getDOMNode().value.trim();
    this.props.onJoin(name);
  },
  render: function() {
    return (
      <form className="joinForm" onSubmit={this.joinGame}>
        <input type="text" placeholder="Your name" ref="name" />
        <input type="submit" value="Join" />
      </form>
    );
  }
});

var Dealing = React.createClass({
  render: function() {
    return this.props.dealer == this.props.player ? (

Draw a card

        <PromptForm onPrompt={this.props.onPrompt} />

      ) : (

Hold tight - {this.props.dealer} is drawing a card.

    );
  }
});

var PromptForm = React.createClass({
  statePrompt: function(e) {
    e.preventDefault();
    var word = this.refs.word.getDOMNode().value.trim();
    this.props.onPrompt(word);
  },
  render: function() {
    return (
      <form className="promptForm" onSubmit={this.statePrompt}>
        <input type="text" placeholder="The word to define" ref="word" />
        <input type="submit" value="Share" />
      </form>
    );
  }
});

var Defining = React.createClass({
  render: function() {
    var playerIsTheDealer = this.props.dealer == this.props.player;
    var theDealerDefinitionExists = -1 < this.props.definitions.map(
        function(def) {
      return def.author;
    }).indexOf(this.props.dealer);
    var thereAreAtLeastThreeDefinitions = 3 <= this.props.definitions.length;
    return (

{this.props.prompt}

        <DefiningForm onDefine={this.props.onDefine} />
        {playerIsTheDealer && theDealerDefinitionExists &&
          thereAreAtLeastThreeDefinitions ? <StartVoting
          onStartVoting={this.props.onStartVoting} /> : ''}

    );
  }
});

var DefiningForm = React.createClass({
  onDefine: function(e) {
    e.preventDefault();
    var definition = this.refs.definition.getDOMNode().value.trim();
    this.props.onDefine(definition);
  },
  render: function() {
    return (
      <form className="defineForm" onSubmit={this.onDefine}>
        <textarea placeholder="Write a definition" ref="definition"></textarea>
        <input type="submit" value="Submit" />
      </form>
    );
  }
});

var StartVoting = React.createClass({
  onStartVoting: function(e) {
    e.preventDefault();
    this.props.onStartVoting();
  },
  render: function() {
    return (
      <form className="startVotingForm" onSubmit={this.onStartVoting}>
        <input type="submit" value="Begin voting" />
      </form>
    );
  }
});

var Voting = React.createClass({
  onEndVoting: function(e) {
    e.preventDefault();
    this.props.onEndVoting();
  },
  render: function() {
    var amDealer = this.props.player == this.props.dealer;
    var definitions = this.props.definitions.filter(function(definition) {
      return amDealer || definition.author != this.props.player;
    }.bind(this));
    if (amDealer) {
      definitionElements = definitions.map(function(definition) {
        return
{definition.definition}
;
      });
      return (

          {definitionElements}
          <button onClick={this.onEndVoting}>End Voting</button>

      );
    };
    return <VoteForm player={this.props.player} definitions={definitions}
      onVote={this.props.onVote} />;
  }
});

var VoteForm = React.createClass({
  onVote: function(votee) {
    return function(e) {
      e.preventDefault();
      this.props.onVote(this.props.player, votee);
    }.bind(this);
  },
  render: function() {
    var definitions = this.props.definitions.map(function(definition) {
      return <input type="button" onClick={this.onVote(definition.author)}
        value={definition.definition} />;
    }.bind(this));
    return (

        {definitions}

    );
  }
});

var EndOfRound = React.createClass({
  onDeal: function(e) {
    e.preventDefault();
    this.props.onDeal();
  },
  render: function() {
    var lastRound = this.props.rounds[this.props.rounds.length - 1];
    var scores = endOfRoundSummary(this.props.players, this.props.rounds);
    return (

        {renderRoundSummary(scores)}
        {this.props.player == lastRound.dealer ?
          <input type="button" onClick={this.onDeal}
            value="Start next round" /> : ''}

    );
  }
});

var Over = React.createClass({
  onReset: function(e) {
    e.preventDefault();
    this.props.onReset();
  },
  render: function() {
    var lastRound = this.props.rounds[this.props.rounds.length - 1];
    var scores = endOfRoundSummary(this.props.players, this.props.rounds);
    return (

        {renderRoundSummary(scores)}
        <input type="button" onClick={this.onReset} value="Start new game" />

    );
  }
});

function playerScoreInRound(player, round) {
  if (round.dealer == player) {
    return 0;
  }
  var timesVotedFor = round.votes.filter(function(vote) {
    return vote.votee == player;
  }).length;
  var voteForRound = round.votes.filter(function(vote) {
    return vote.voter == player;
  })[0];
  var votedCorrectly = !!(voteForRound && voteForRound.votee == round.dealer);
  return 2 * timesVotedFor + +votedCorrectly;
}

function endOfRoundSummary(players, rounds) {
  var lastRound = rounds[rounds.length - 1];
  var scores = {};
  players.forEach(function(player) {
    scores[player] = {};
  });
  lastRound.definitions.forEach(function(definition) {
    scores[definition.author].definition = definition.definition;
  });
  players.forEach(function(player) {
    scores[player].roundScore = playerScoreInRound(player, lastRound);
    scores[player].totalScore = rounds.map(function(round) {
      return playerScoreInRound(player, round);
    }).reduce(function(acc, cur) {
      return acc + cur;
    });
  });
  return scores;
}

function renderRoundSummary(summary) {
  var playerRows = Object.keys(summary).map(function(player) {
    score = summary[player];
    return (

        {player}
        {score.roundScore}
        {score.totalScore}
        {score.definition}

    );
  });
  return (

        Player
        Round
        Total
        Definition

      {playerRows}

  );
}

React.render(
  <DeckAround url="status" pollInterval={1000}/>,
  document.getElementById('content')
);
<|endoftext|>


<commit_before>// According to the Wikipedia's article: "The Game of Life, also known simply as Life,
// is a cellular automaton devised by the British mathematician John Horton Conway in 1970."

// Given a board with m by n cells, each cell has an initial state live (1) or dead (0).
// Each cell interacts with its eight neighbors (horizontal, vertical, diagonal) using the following four rules
// (taken from the above Wikipedia article):

// Any live cell with fewer than two live neighbors dies, as if caused by under-population.
// Any live cell with two or three live neighbors lives on to the next generation.
// Any live cell with more than three live neighbors dies, as if by over-population..
// Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
// Write a function to compute the next state (after one update) of the board given its current state.

var gameOfLife = function(board) {
    var result = [],
        i,
        j;

    for ( i = 0; i < board.length; i++ ) {
      result.push( [] );
    }

    for( i = 0; i < board.length; i++ ){ //
        for( j = 0; j < board[i].length; j++ ){ // cell
                // var row = board[i],
                //     cell = board[i][j];
                result[i][j] = 0;
                if( board[i][j+1] === 1 || board[i][j-1] === 1 ) result[i][j] = 1; // horizontal check
                if( board[i+1][j] === 1 || board[i-1][j] === 1) result[i][j] = 1; // vertical check
                if( board[i+1][j+1] === 1 || board[i-1][j+1] === 1) result[i][j] = 1; // rdiag check
                if( board[i-1][j-1] === 1 || board[i+1][j-1] === 1) result[i][j] = 1; // ldiag check

        }// end cell
    } // end row
    return result;
};
// i = row; j = cell
// horizontal[i][j] j + 1 || j - 1
// vertical[i][j] i + 1 || i - 1
// rdiag[i][j] i+1 && j+1 || i-1 && j+1
// rdiag[i][j] i-1 && j-1 || i+1 && j-1
//[00,01,02]
//[10,11,12]
//[20,21,22]
<commit_msg>add soltuion<commit_after>// According to the Wikipedia's article: "The Game of Life, also known simply as Life,
// is a cellular automaton devised by the British mathematician John Horton Conway in 1970."

// Given a board with m by n cells, each cell has an initial state live (1) or dead (0).
// Each cell interacts with its eight neighbors (horizontal, vertical, diagonal) using the following four rules
// (taken from the above Wikipedia article):

// Any live cell with fewer than two live neighbors dies, as if caused by under-population.
// Any live cell with two or three live neighbors lives on to the next generation.
// Any live cell with more than three live neighbors dies, as if by over-population..
// Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
// Write a function to compute the next state (after one update) of the board given its current state.

var gameOfLife = function(board) {
    var result = [],
        i,
        j;

    for ( i = 0; i < board.length; i++ ) {
      result.push( [] );
    }

    for( i = 0; i < board.length; i++ ){ //
        for( j = 0; j < board[i].length; j++ ){ // cell
                // var row = board[i],
                //     cell = board[i][j];
                result[i][j] = 0;
                if( board[i][j+1] )
                    if( board[i][j+1] === 1 ) result[i][j] = 1;  // horizontal r check
                if( board[i][j-1] )
                    if( board[i][j-1] === 1 ) result[i][j] = 1; // horizontal l check
                if( board[i-1] ){
                    if( board[i-1][j] === 1 ) result[i][j] = 1; // vertical t check
                    if( board[i-1][j+1] )
                        if( board[i-1][j+1] === 1 ) result[i][j] = 1; // rdiag t check
                    if( board[i-1][j-1] )
                        if( board[i-1][j-1] === 1 ) result[i][j] = 1; // ldiag t check
                }
                if( board[i+1] ){
                    if( board[i+1][j] === 1 ) result[i][j] = 1; // vertical b check
                    if( board[i+1][j+1] )
                        if( board[i+1][j+1] === 1 ) result[i][j] = 1; // rdiag b check
                    if( board[i+1][j-1] )
                        if( board[i+1][j-1] === 1 ) result[i][j] = 1; // ldiag t check
                }
        }// end cell
    } // end row
    return result;
};
var arr = [
          [1,0,1],
          [1,0,0],
          [0,0,0]
          ]
console.timeEnd( gameOfLife(arr) );
// i = row; j = cell
// horizontal[i][j] j + 1 || j - 1
// vertical[i][j] i + 1 || i - 1
// rdiag[i][j] i+1 && j+1 || i-1 && j+1
// rdiag[i][j] i-1 && j-1 || i+1 && j-1
//[00,01,02]
//[10,11,12]
//[20,21,22]
<|endoftext|>


<commit_before>import { Damage } from '../damage';
import { Team, isTeam } from '../utility/team';
import * as matrices from '../utility/matrices';
import { Creature } from '../creature';
import { Effect } from '../effect';

/** Creates the abilities
 * @param {Object} G the game object
 * @return {void}
 */
export default (G) => {
	G.abilities[33] = [
		// 	First Ability: Battle Cry
		{
			//	Type : Can be "onQuery", "onStartPhase", "onDamage"
			trigger: 'onDamage onStartPhase',

			_damaged: false,
			_targets: [],

			// 	require() :
			require: function () {
				// Creature is damaged
				if (G.activeCreature != this.creature) {
					this._damaged = true;
					return false;
				}
				if (!this._damaged) {
					return false;
				} else {
					this._damaged = false;
				}
				this._targets = this.creature.adjacentHexes(1);
				if (this.isUpgraded()) {
					// Upgraded version only activates if enemy is in adjacent hexes
					if (
						!this.atLeastOneTarget(this._targets, {
							team: Team.enemy,
						})
					) {
						return false;
					}
				}
				return this.testRequirements();
			},

			//	activate() :
			activate: function () {
				let creature = this.creature;
				let damage = new Damage(creature, { sonic: 30 }, this._targets.length, [], G);
				let hits = new Set();

				this._targets.forEach((target) => {
					if (target.creature === undefined || hits.has(target.creature)) {
						return;
					}
					hits.add(target.creature);
				});
				this.end(false, true);
				hits.forEach((hit, _, set) => {
					hit.takeDamage(damage);
				});
				this.end(true, false);
			},
		},

		/**
		 * Second Ability: Executioner Axe
		 *
		 * Damage an adjacent enemy unit with slash damage, instantly killing it if
		 * its remaining health is <= 45.
		 *
		 * Targeting rules:
		 * - The target unit be an enemy.
		 * - The target unit must be in hexes directly adjacent to the Golden Wyrm.
		 *
		 * Other rules:
		 * - If the unit has 45 remaining health or lower, it will be instantly killed
		 *   unless protected by the Dodge or Shielded damage mitigation effects.
		 * - The execution damage is "pure" and cannot be reduced via resistances.
		 *
		 * When upgraded, a successful execution attack will allow the ability to be
		 * used again. Each use requires the full 25 energy cost.
		 */
		{
			trigger: 'onQuery',

			_executeHealthThreshold: 45,

			_targetTeam: Team.enemy,

			require: function () {
				if (!this.testRequirements()) {
					return false;
				}

				if (
					!this.atLeastOneTarget(this._getHexes(), {
						team: this._targetTeam,
					})
				) {
					return false;
				}

				return true;
			},

			query: function () {
				const wyrm = this.creature;
				const ability = this;

				this.game.grid.queryCreature({
					fnOnConfirm: function () {
						ability.animation(...arguments);
					},
					team: this._targetTeam,
					id: wyrm.id,
					flipped: wyrm.flipped,
					hexes: this._getHexes(),
				});
			},

			activate: function (target) {
				this.end();

				if (target.health <= this._executeHealthThreshold) {
					const executeDamage = new Damage(
						this.creature,
						{
							// Pure damage bypasses resistances, but can still be shielded or dodged.
							pure: target.health,
						},
						1,
						[],
						this.game,
					);
					/* Suppress the death message, to be replaced by a custom log. Setting
					`noLog` on Damage wouldn't work as it would suppress Shielded/Dodged messages. */
					this.game.UI.chat.suppressMessage(/is dead/i, 1);
					const damageResult = target.takeDamage(executeDamage);

					// Damage could be shielded or blocked, so double check target has died.
					if (damageResult.kill) {
						this.game.log(`%CreatureName${target.id}% has been executed!`);
						target.hint('Executed', 'damage');

						if (this.isUpgraded()) {
							this.setUsed(false);
							// Refresh UI to show ability still able to be used.
							this.game.UI.selectAbility(-1);
						}
					}
				} else {
					const normalDamage = new Damage(
						this.creature,
						{
							slash: 30,
						},
						1,
						[],
						this.game,
					);
					target.takeDamage(normalDamage);
				}
			},

			/**
			 * Determine the area of effect to query and activate the ability. The area
			 * of effect are the hexes directly adjacent around the 3-sized creature.
			 * This is a total of 10 hexes assuming the Golden Wyrm is not adjacent to
			 * the edge of the board.
			 *
			 * @returns {Hex[]} Refer to Creature.adjacentHexes()
			 */
			_getHexes() {
				return this.creature.adjacentHexes(1);
			},
		},

		// 	Third Ability: Dragon Flight
		{
			//	Type : Can be "onQuery", "onStartPhase", "onDamage"
			trigger: 'onQuery',

			require: function () {
				return this.testRequirements();
			},

			fnOnSelect: function (hex) {
				this.creature.tracePosition({
					x: hex.x,
					y: hex.y,
					overlayClass: 'creature moveto selected player' + this.creature.team,
				});
			},

			// 	query() :
			query: function () {
				let ability = this;
				let wyrm = this.creature;

				let range = G.grid
					.getFlyingRange(wyrm.x, wyrm.y, 50, wyrm.size, wyrm.id)
					.filter((item) => wyrm.item == item.y);

				G.grid.queryHexes({
					fnOnSelect: function () {
						ability.fnOnSelect(...arguments);
					},
					fnOnConfirm: function () {
						ability.animation(...arguments);
					},
					size: wyrm.size,
					flipped: wyrm.player.flipped,
					id: wyrm.id,
					hexes: range,
				});
			},

			//	activate() :
			activate: function (hex) {
				let ability = this;
				ability.end();

				ability.creature.moveTo(hex, {
					ignoreMovementPoint: true,
					ignorePath: true,
					callback: function () {
						G.activeCreature.queryMove();
					},
				});

				// Frogger Leap bonus
				ability.creature.addEffect(
					new Effect(
						'Offense++', // Name
						ability.creature, // Caster
						ability.creature, // Target
						'onStepIn onEndPhase', // Trigger
						{
							effectFn: function (effect) {
								effect.deleteEffect();
							},
							alterations: {
								offense: 25,
							},
						}, // Optional arguments
						G,
					),
				);
			},
		},

		/**
		 * Fourth Ability: Visible Stigmata
		 *
		 * Heals an adjacent allied unit at the cost of the Golden Wyrm's own health,
		 * essentially transferring health.
		 *
		 * Targeting rules:
		 * - The target unit be an ally.
		 * - The target unit must be in hexes directly adjacent to the Golden Wyrm.
		 * - Cannot target full health units.
		 *
		 * Other rules:
		 * - Requires at least 51 remaining health to use so the Golden Wyrm doesn't
		 *   kill itself.
		 * - If the target unit is missing less than the max heal amount (50), only
		 *   the missing amount will be transferred.
		 *
		 * When upgraded each use buffs Golden Wyrm with 10 permanent regrowth points.
		 */
		{
			trigger: 'onQuery',

			_targetTeam: Team.ally,

			_maxTransferAmount: 50,

			require: function () {
				if (!this.testRequirements()) {
					return false;
				}

				/* Golden Wyrm needs to be left with at least 1 hp after the ability. We
				can't use ability.costs because that is a fixed value, whereas this ability
				has a dynamic health cost. */
				if (this.creature.health < this._maxTransferAmount + 1) {
					this.message = 'Not enough health';
					return false;
				}

				if (
					!this.atLeastOneTarget(this._getHexes(), {
						team: this._targetTeam,
						optTest: this._confirmTarget,
					})
				) {
					return false;
				}

				return true;
			},

			query: function () {
				const ability = this;
				const wyrm = this.creature;

				G.grid.queryCreature({
					fnOnConfirm: function () {
						ability.animation(...arguments);
					},
					optTest: this._confirmTarget,
					team: this._targetTeam,
					id: wyrm.id,
					flipped: wyrm.flipped,
					hexes: this._getHexes(),
				});
			},

			activate: function (target) {
				this.end();

				// The health transferred is the creature's missing life, capped to 50.
				const transferAmount = Math.min(
					target.stats.health - target.health,
					this._maxTransferAmount,
				);

				target.heal(transferAmount, false, false);

				/* Damage Golden Wyrm using `.heal()` instead of `.takeDamage()` to apply
				raw damage that bypasses dodge, shielded, etc and does not trigger further
				effects. */
				this.creature.heal(-transferAmount, false, false);

				// Rather than individual loss/gain health logs, show a single custom log.
				this.game.log(
					`%CreatureName${this.creature.id}% gives ${transferAmount} health to %CreatureName${target.id}%`,
				);

				if (this.isUpgraded()) {
					const regrowthBuffEffect = new Effect(
						this.title,
						this.creature,
						this.creature,
						'',
						{
							alterations: {
								regrowth: 10,
							},
						},
						G,
					);

					this.creature.addEffect(
						regrowthBuffEffect,
						`%CreatureName${this.creature.id}% gains 10 regrowth points`,
						'',
						false,
						true,
					);
				}
			},

			/**
			 * Determine the area of effect to query and activate the ability. The area
			 * of effect are the hexes directly adjacent around the 3-sized creature.
			 * This is a total of 10 hexes assuming the Golden Wyrm is not adjacent to
			 * the edge of the board.
			 *
			 * @returns {Hex[]} Refer to Creature.adjacentHexes()
			 */
			_getHexes() {
				return this.creature.adjacentHexes(1);
			},

			/**
			 * Confirm a target ally creature can be targeted.
			 *
			 * @param {Creature} creature Ally creature that could be targeted.
			 * @returns {boolean} Should the creature be targeted?
			 */
			_confirmTarget(creature) {
				return creature.health < creature.stats.health;
			},
		},
	];
};
<commit_msg>Change ability targeting<commit_after>import { Damage } from '../damage';
import { Team, isTeam } from '../utility/team';
import * as matrices from '../utility/matrices';
import { Creature } from '../creature';
import { Effect } from '../effect';

/** Creates the abilities
 * @param {Object} G the game object
 * @return {void}
 */
export default (G) => {
	G.abilities[33] = [
		// 	First Ability: Battle Cry
		{
			//	Type : Can be "onQuery", "onStartPhase", "onDamage"
			trigger: 'onDamage onStartPhase',

			_damaged: false,
			_targets: [],

			// 	require() :
			require: function () {
				// Creature is damaged
				if (G.activeCreature != this.creature) {
					this._damaged = true;
					return false;
				}
				if (!this._damaged) {
					return false;
				} else {
					this._damaged = false;
				}
				this._targets = this.creature.adjacentHexes(1);
				if (this.isUpgraded()) {
					// Upgraded version only activates if enemy is in adjacent hexes
					if (
						!this.atLeastOneTarget(this._targets, {
							team: Team.enemy,
						})
					) {
						return false;
					}
				}
				return this.testRequirements();
			},

			//	activate() :
			activate: function () {
				let creature = this.creature;
				let damage = new Damage(creature, { sonic: 30 }, this._targets.length, [], G);
				let hits = new Set();

				this._targets.forEach((target) => {
					if (target.creature === undefined || hits.has(target.creature)) {
						return;
					}
					hits.add(target.creature);
				});
				this.end(false, true);
				hits.forEach((hit, _, set) => {
					hit.takeDamage(damage);
				});
				this.end(true, false);
			},
		},

		/**
		 * Second Ability: Executioner Axe
		 *
		 * Damage an adjacent enemy unit with slash damage, instantly killing it if
		 * its remaining health is <= 45.
		 *
		 * Targeting rules:
		 * - The target unit be an enemy.
		 * - The target unit must be in the 3 front hexes, or 3 back hexes adjacent
		 *   to the Golden Wyrm.
		 *
		 * Other rules:
		 * - If the unit has 45 remaining health or lower, it will be instantly killed
		 *   unless protected by the Dodge or Shielded damage mitigation effects.
		 * - The execution damage is "pure" and cannot be reduced via resistances.
		 *
		 * When upgraded, a successful execution attack will allow the ability to be
		 * used again. Each use requires the full 25 energy cost.
		 */
		{
			trigger: 'onQuery',

			_executeHealthThreshold: 45,

			_targetTeam: Team.enemy,

			require: function () {
				if (!this.testRequirements()) {
					return false;
				}

				if (
					!this.atLeastOneTarget(this._getHexes(), {
						team: this._targetTeam,
					})
				) {
					return false;
				}

				return true;
			},

			query: function () {
				const wyrm = this.creature;
				const ability = this;

				this.game.grid.queryCreature({
					fnOnConfirm: function () {
						ability.animation(...arguments);
					},
					team: this._targetTeam,
					id: wyrm.id,
					flipped: wyrm.flipped,
					hexes: this._getHexes(),
				});
			},

			activate: function (target) {
				this.end();

				if (target.health <= this._executeHealthThreshold) {
					const executeDamage = new Damage(
						this.creature,
						{
							// Pure damage bypasses resistances, but can still be shielded or dodged.
							pure: target.health,
						},
						1,
						[],
						this.game,
					);
					/* Suppress the death message, to be replaced by a custom log. Setting
					`noLog` on Damage wouldn't work as it would suppress Shielded/Dodged messages. */
					this.game.UI.chat.suppressMessage(/is dead/i, 1);
					const damageResult = target.takeDamage(executeDamage);

					// Damage could be shielded or blocked, so double check target has died.
					if (damageResult.kill) {
						this.game.log(`%CreatureName${target.id}% has been executed!`);
						target.hint('Executed', 'damage');

						if (this.isUpgraded()) {
							this.setUsed(false);
							// Refresh UI to show ability still able to be used.
							this.game.UI.selectAbility(-1);
						}
					}
				} else {
					const normalDamage = new Damage(
						this.creature,
						{
							slash: 30,
						},
						1,
						[],
						this.game,
					);
					target.takeDamage(normalDamage);
				}
			},

			/**
			 * The area of effect is the front and back 3 hexes for a total of 6 hexes.
			 *
			 * @returns {Hex[]} Refer to Creature.getHexMap()
			 */
			_getHexes() {
				return this.creature.getHexMap(matrices.frontnback3hex);
			},
		},

		// 	Third Ability: Dragon Flight
		{
			//	Type : Can be "onQuery", "onStartPhase", "onDamage"
			trigger: 'onQuery',

			require: function () {
				return this.testRequirements();
			},

			fnOnSelect: function (hex) {
				this.creature.tracePosition({
					x: hex.x,
					y: hex.y,
					overlayClass: 'creature moveto selected player' + this.creature.team,
				});
			},

			// 	query() :
			query: function () {
				let ability = this;
				let wyrm = this.creature;

				let range = G.grid
					.getFlyingRange(wyrm.x, wyrm.y, 50, wyrm.size, wyrm.id)
					.filter((item) => wyrm.item == item.y);

				G.grid.queryHexes({
					fnOnSelect: function () {
						ability.fnOnSelect(...arguments);
					},
					fnOnConfirm: function () {
						ability.animation(...arguments);
					},
					size: wyrm.size,
					flipped: wyrm.player.flipped,
					id: wyrm.id,
					hexes: range,
				});
			},

			//	activate() :
			activate: function (hex) {
				let ability = this;
				ability.end();

				ability.creature.moveTo(hex, {
					ignoreMovementPoint: true,
					ignorePath: true,
					callback: function () {
						G.activeCreature.queryMove();
					},
				});

				// Frogger Leap bonus
				ability.creature.addEffect(
					new Effect(
						'Offense++', // Name
						ability.creature, // Caster
						ability.creature, // Target
						'onStepIn onEndPhase', // Trigger
						{
							effectFn: function (effect) {
								effect.deleteEffect();
							},
							alterations: {
								offense: 25,
							},
						}, // Optional arguments
						G,
					),
				);
			},
		},

		/**
		 * Fourth Ability: Visible Stigmata
		 *
		 * Heals an adjacent allied unit at the cost of the Golden Wyrm's own health,
		 * essentially transferring health.
		 *
		 * Targeting rules:
		 * - The target unit be an ally.
		 * - The target unit must be in hexes directly adjacent to the Golden Wyrm.
		 * - Cannot target full health units.
		 *
		 * Other rules:
		 * - Requires at least 51 remaining health to use so the Golden Wyrm doesn't
		 *   kill itself.
		 * - If the target unit is missing less than the max heal amount (50), only
		 *   the missing amount will be transferred.
		 *
		 * When upgraded each use buffs Golden Wyrm with 10 permanent regrowth points.
		 */
		{
			trigger: 'onQuery',

			_targetTeam: Team.ally,

			_maxTransferAmount: 50,

			require: function () {
				if (!this.testRequirements()) {
					return false;
				}

				/* Golden Wyrm needs to be left with at least 1 hp after the ability. We
				can't use ability.costs because that is a fixed value, whereas this ability
				has a dynamic health cost. */
				if (this.creature.health < this._maxTransferAmount + 1) {
					this.message = 'Not enough health';
					return false;
				}

				if (
					!this.atLeastOneTarget(this._getHexes(), {
						team: this._targetTeam,
						optTest: this._confirmTarget,
					})
				) {
					return false;
				}

				return true;
			},

			query: function () {
				const ability = this;
				const wyrm = this.creature;

				G.grid.queryCreature({
					fnOnConfirm: function () {
						ability.animation(...arguments);
					},
					optTest: this._confirmTarget,
					team: this._targetTeam,
					id: wyrm.id,
					flipped: wyrm.flipped,
					hexes: this._getHexes(),
				});
			},

			activate: function (target) {
				this.end();

				// The health transferred is the creature's missing life, capped to 50.
				const transferAmount = Math.min(
					target.stats.health - target.health,
					this._maxTransferAmount,
				);

				target.heal(transferAmount, false, false);

				/* Damage Golden Wyrm using `.heal()` instead of `.takeDamage()` to apply
				raw damage that bypasses dodge, shielded, etc and does not trigger further
				effects. */
				this.creature.heal(-transferAmount, false, false);

				// Rather than individual loss/gain health logs, show a single custom log.
				this.game.log(
					`%CreatureName${this.creature.id}% gives ${transferAmount} health to %CreatureName${target.id}%`,
				);

				if (this.isUpgraded()) {
					const regrowthBuffEffect = new Effect(
						this.title,
						this.creature,
						this.creature,
						'',
						{
							alterations: {
								regrowth: 10,
							},
						},
						G,
					);

					this.creature.addEffect(
						regrowthBuffEffect,
						`%CreatureName${this.creature.id}% gains 10 regrowth points`,
						'',
						false,
						true,
					);
				}
			},

			/**
			 * Determine the area of effect to query and activate the ability. The area
			 * of effect are the hexes directly adjacent around the 3-sized creature.
			 * This is a total of 10 hexes assuming the Golden Wyrm is not adjacent to
			 * the edge of the board.
			 *
			 * @returns {Hex[]} Refer to Creature.adjacentHexes()
			 */
			_getHexes() {
				return this.creature.adjacentHexes(1);
			},

			/**
			 * Confirm a target ally creature can be targeted.
			 *
			 * @param {Creature} creature Ally creature that could be targeted.
			 * @returns {boolean} Should the creature be targeted?
			 */
			_confirmTarget(creature) {
				return creature.health < creature.stats.health;
			},
		},
	];
};
<|endoftext|>