isundil пре 6 година
родитељ
комит
a634612b4f
6 измењених фајлова са 700 додато и 39 уклоњено
  1. 11 3
      config.js
  2. 15 0
      iBot.js
  3. 73 15
      index.js
  4. 579 0
      loupgarou.js
  5. 11 8
      quizz.js
  6. 11 13
      rapido.js

+ 11 - 3
config.js

@@ -31,8 +31,8 @@ module.exports.MODULES["#quizz"] = new (require('./quizz.js'))({
     MySQL_PERIOD_TABLE: "knackizz_period",
     MySQL_SCORES_TABLE: "knackizz_scores",
 });
-
-module.exports.MODULES["#rapido"] = new (require('./rapido.js'))({
+module.exports.MODULES["#rapido"] = [];
+module.exports.MODULES["#rapido"].push(new (require('./rapido.js'))({
     DISABLED: false,
     NEXT_WORD_DELAY: 10 * 1000, // 10 sec
     WORD_TIMEO_FPS: 0.5, // 2 seconds per key
@@ -50,7 +50,15 @@ module.exports.MODULES["#rapido"] = new (require('./rapido.js'))({
         [ 1, 1 ]
     ],
     GAME_DURATION: 1 * 60 * 60 * 1000 // 1 hour game duration
-});
+}));
+
+module.exports.MODULES["#rapido"].push(new (require('./loupgarou.js'))({
+    DISABLED: false,
+    registrationDelay: 2 * 60, // 2 min
+    cupidonDelay: 60, // 1 min
+    reminderInterval: 30, // 30 sec
+    privateChannel: "#jeux-lg"
+}));
 
 module.exports.MODULES["#voicefaible"] = new (require('./levoicefaible.js'))({
 });

+ 15 - 0
iBot.js

@@ -0,0 +1,15 @@
+
+function IBot() {}
+
+IBot.prototype.init = function(bot, chanName){};
+IBot.prototype.getName = function(){};
+IBot.prototype.onActivate = function(){};
+IBot.prototype.onMessage = function(user, message){};
+IBot.prototype.onAddMode = function(user, mode) {};
+IBot.prototype.onRemMode = function(user, mode) {};
+IBot.prototype.onRename = function(oldNick, newNick) {};
+IBot.prototype.onNickPart = function(nick) {};
+IBot.prototype.onNameList = function(nicks) {};
+IBot.prototype.onSelfJoin = function() {};
+IBot.prototype.onJoin = funcion(nick) {};
+

+ 73 - 15
index.js

@@ -26,15 +26,20 @@ function KnackiBot() {
     this.password = NS_PASSWORD;
     this.modules = MODULES;
 
-    for (var i in this.modules)
-        this.modules[i].init(this, i);
-
     this.bot = new irc.Client(IRC_HOSTNAME, this.name, {
         channels: Object.keys(this.modules),
         userName: this.name,
         realName: this.name,
         stripColors: true
     });
+    for (var i in this.modules) {
+        if (!Array.isArray(this.modules[i]))
+            this.modules[i] = [this.modules[i]];
+        this.modules[i].activeGame = this.modules[i].length == 1 ? this.modules[i][0] : null;
+        if (this.modules[i].activeGame)
+            this.modules[i].activeGame.onActivate();
+        this.modules[i].forEach(mod => mod.init(this, i));
+    }
     if (USE_NS) {
         var _this = this,
             registerHandler = 0;
@@ -57,6 +62,7 @@ function KnackiBot() {
                     if (registerHandler !== undefined) {
                         clearInterval(registerHandler);
                         registerHandler = undefined;
+                        _this.setModes(this.name, "-R");
                     }
                 }
             }
@@ -71,46 +77,85 @@ function KnackiBot() {
         chan = chan.toLowerCase();
         if (_this.modules[chan]) {
             if (nick == _this.name)
-                _this.modules[chan].onSelfJoin();
+                _this.modules[chan].forEach(i => i.onSelfJoin());
             else
-                _this.modules[chan].onJoin(nick);
+                _this.modules[chan].forEach(i => i.onJoin(nick));
         }
     });
     this.bot.addListener("names", (chan, nicks) => {
         chan = chan.toLowerCase();
-        _this.modules[chan] && _this.modules[chan].onNameList(nicks);
+        _this.modules[chan] && _this.modules[chan].forEach(i => i.onNameList(nicks));
     });
     this.bot.addListener("part", (chan, nick) => {
         chan = chan.toLowerCase();
-        _this.modules[chan] && _this.modules[chan].onNickPart(nick);
+        _this.modules[chan] && _this.modules[chan].forEach(i => i.onNickPart(nick));
     });
     this.bot.addListener("kick", (chan, nick) => {
         chan = chan.toLowerCase();
-        _this.modules[chan] && _this.modules[chan].onNickPart(nick);
+        _this.modules[chan] && _this.modules[chan].forEach(i => i.onNickPart(nick));
     });
     this.bot.addListener("nick", (oldNick, newNick) => {
-        for (var i in _this.modules)
-            _this.modules[i].onRename(oldNick, newNick);
+        if (this.name === oldNick)
+            this.name = newNick;
+        else
+            for (var i in _this.modules)
+                _this.modules[chan] && _this.modules[i].forEach(i => i.onRename(oldNick, newNick));
     });
     this.bot.addListener("+mode", (chan, by, mode, user) => {
         chan = chan.toLowerCase();
-        user && _this.modules[chan] && _this.modules[chan].onAddMode(user, mode);
+        user && _this.modules[chan] && _this.modules[chan].forEach(i => i.onAddMode(user, mode));
     });
     this.bot.addListener("-mode", (chan, by, mode, user) => {
         chan = chan.toLowerCase();
-        user && _this.modules[chan] && _this.modules[chan].onRemMode(user, mode);
+        user && _this.modules[chan] && _this.modules[chan].forEach(i => i.onRemMode(user, mode));
     });
     this.bot.addListener("message", (user, room, text) => {
         room = room.toLowerCase();
-        _this.modules[room] && _this.modules[room].onMessage(user, text);
+        if (_this.modules[room]) {
+            if (text.substr(0, 1) == "!" && !_this.modules[room].activeGame) {
+                var found = false;
+                for (var i =0, len = _this.modules[room].length; i < len; ++i) {
+                    if ('!' +_this.modules[room][i].getName() === text.trim().toLowerCase()) {
+                        console.info("Loading game " +text);
+                        _this.modules[room].activeGame = _this.modules[room][i];
+                        _this.modules[room].activeGame.onActivate();
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    this.sendMsg(room, "Jeux disponibles: " +_this.modules[room].map(i => '!' +i.getName()).join(", "));
+                    return;
+                }
+            }
+            if (_this.modules[room].activeGame)
+                _this.modules[room].activeGame.onMessage(user, text);
+        }
     });
 }
 
+KnackiBot.prototype.endGame = function(game) {
+    for (var i in this.modules)
+        if (this.modules[i].activeGame && this.modules[i].activeGame === game) {
+            this.modules[i].activeGame = null;
+            return;
+        }
+}
+
 KnackiBot.prototype.createUser = function(nick) {
     return new User(nick);
 }
 
-KnackiBot.prototype.voice = function(chan, username) {
+KnackiBot.prototype.kick = function(chan, pseudo) {
+    console.info("Kicking " +pseudo +" from " +chan);
+    this.bot.send.call(this.bot, "KICK", chan, pseudo);
+}
+
+KnackiBot.prototype.setModes = function(chan, modes) {
+    this.bot.send.call(this.bot, "MODE", chan, modes);
+}
+
+KnackiBot.prototype.setVoice = function(chan, username, direction) {
     var usernames = Array.isArray(username) ? username : [ username ];
     if (usernames.length > 10)
     {
@@ -118,10 +163,23 @@ KnackiBot.prototype.voice = function(chan, username) {
         this.voice(chan, usernames.slice(10));
         return;
     }
-    usernames.splice(0, 0, "MODE", chan, "+" +"v".repeat(usernames.length));
+    usernames.splice(0, 0, "MODE", chan, (direction > 0 ? '+' : '-') +"v".repeat(usernames.length));
     this.bot.send.apply(this.bot, usernames);
 }
 
+
+KnackiBot.prototype.voice = function(chan, username) {
+    this.setVoice(chan, username, 1);
+}
+
+KnackiBot.prototype.devoice = function(chan, username) {
+    this.setVoice(chan, username, -1);
+}
+
+KnackiBot.prototype.invite = function(channel, pseudo) {
+    this.bot.send.call(this.bot, "INVITE", pseudo, channel);
+}
+
 KnackiBot.prototype.sendNotice = function(username, msg) {
     this.bot.notice(username, msg);
 }

+ 579 - 0
loupgarou.js

@@ -0,0 +1,579 @@
+
+function LoupGarou(config) {
+    this.config = config;
+}
+
+const ROLES = {
+    WOLF: "WOLF",
+    VILLAGER: "VILLAGER",
+    CUPIDON: "CUPIDON",
+    HUNTER: "HUNTER",
+    WITCH: "WITCH",
+    SEER: "SEER",
+    GIRL: "GIRL"
+};
+
+const ROLE_REPARTITION = [
+    [ ROLES.WOLF, ROLES.CUPIDON, ROLES.VILLAGER ],
+    [ ROLES.WOLF, ROLES.VILLAGER, ROLES.VILLAGER, ROLES.VILLAGER ],
+    [ ROLES.WOLF, ROLES.WOLF, ROLES.VILLAGER, ROLES.VILLAGER, ROLES.HUNTER ],
+    [ ROLES.WOLF, ROLES.WOLF, ROLES.VILLAGER, ROLES.VILLAGER, ROLES.HUNTER, ROLES.WITCH ]
+];
+
+var i =-1;
+const STEP = {
+    NOT_STARTED: -4,
+    REGISTRATION: -3,
+    SET_ROLES: -2,
+    SELECT_CUPIDON: -1,
+    NIGHT: ++i,
+    NIGHT_SEER: ++i,
+    NIGHT_GIRL: ++i,
+    NIGHT_LG: ++i,
+    NIGHT_WITCH: ++i,
+    DAY_LOVER: ++i,
+    DAY_HUNTER: ++i,
+    DAY: ++i,
+    DAY_VILLA: ++i,
+    DAY_EVENING: ++i
+};
+const MAX_STEP = STEP.DAY_EVENING;
+
+const langMng = [
+    {
+        unregisterLeft: function(who) { return `${who} n'est plus inscrit au Loup-Garou`; },
+        killedHunter: function(who) { return `Le chasseur tire sur ${who} dans son dernier souffle et le tue !`; },
+        killedWolf: function(who) { return `${who} a servi d'en-cas aux loups-garous !`; },
+        killedLeft: function(who) { return `${who} souffre du raj-quit et en est éliminé...`; },
+        killedLove: function(who, lover) { return `Dans un élan de chagrin, ${who} decide de rejoindre ${lover} dans sa tombe. RIP !`; },
+        killedVillager: function(who) { return `Vous avez décidé de voter contre ${who} et le tuez sans le moindre remord.`; },
+        join: function(who) { return `${who} est inscrit.e a la prochaine partie !`; },
+        inviteWolfRoom: function() { return `Invitation des Loups Garous sur le salon de nuit.`; },
+        inviteWolfRoomPrivate: function(salon) { return "Merci de rejoindre le salon " +salon; },
+        dayNoDead: function() { return "Il fait jour et le village se réveille et personne n'est mort."; },
+        dayOneDead: function() { return "Il fait jour et le village se réveille en découvrant un corps sans vie près de la fontaine."; },
+        dayMultiDead: function() { return "Il fait jour et le village se réveille en découvrant avec stupeur et tremblement une pile de corps au centre de la place du marché."; },
+        startVotes: function() { return "Vous allez devoir trouver les loups garous et les éliminer. Pour voter contre quelqu'un tape : !vote <pseudo>"; },
+        cupidonMessage: function(players) { return "Tu vas pouvoir choisir un couple d'amoureux. Pour envoyer ta fleche, tapes les pseudos des deux personnes a coupler. Tu as le choix entre: " +players.join(", "); },
+        setNoLove: function() { return "Tu n'as planté ta flèche dans personne. Il n'y aura donc pas d'amoureux cette partie."; },
+        setLoveWith: function(p1, p2) { return `Tu as planté ta flèche et choisi ${p1} et ${p2}`; },
+        inLoveWith: function(other) { return `Cupidon a envoyé sa flèche sur toi et ton amoureux est ${other}`; },
+        remainingPseudos: function(nicks) { return "Vous avez le choix entre : " +nicks.join(", "); },
+        loverWins: function() { return "Les amoureux gagnent la partie !"; },
+        wolfWins: function() { return "Les loup-garous gagnent la partie !"; },
+        villagerWins: function() { return "Les villageois gagnent la partie !"; },
+        congrats: function(nickList) { return nickList.length == 1 ? ("Félicitations au gagnant " +nickList[0]) : ("Félicitations aux gagnants " +nickList.join(", ")); },
+        waitingCupidon: function(timeSec) { return `En attente de cupidon (${timeSec} secondes)`; },
+        whowaswhat: {
+            "WOLF": function(who) { return `${who} était un Loup-Garou !`; },
+            "VILLAGER": function(who) { return `${who} était un simple villageois !`; },
+            "CUPIDON": function(who) { return `${who} était un cupidon !`; },
+            "HUNTER": function(who) { return `${who} était un chasseur !`; },
+            "WITCH": function(who) { return `${who} était une sorcière !`; },
+            "SEER": function(who) { return `${who} était une voyante !`; },
+            "GIRL": function(who) { return `${who} était une petite fille !`; },
+        },
+        advRole: {
+            "WOLF": "Vous êtes un Loup-Garou ! Votre objectif est d'éliminer tous les innocents (ceux qui ne sont pas Loups Garous). Chaque nuit, vous vous réunissez entre compères Loups pour décider d'une victime à éliminer.",
+            "VILLAGER": "Vous êtes un Villageois ! Votre objectif est d'éliminer tous les Loups Garous. Vous ne disposez d'aucun pouvoir particulier : uniquement votre perspicacité et votre force de persuasion.",
+            "CUPIDON": "Vous êtes Cupidon ! Votre objectif est d'éliminer tous les Loups Garous. Dès le début de la partie, vous devez former un couple de deux joueurs. Leur objectif sera de survivre ensemble, car si l'un d'eux meurt, l'autre se suicidera.",
+            "HUNTER": "Vous êtes le chasseur ! Votre objectif est d'éliminer tous les Loups Garous. A votre mort, vous pouvez éliminer un joueur en utilisant votre dernière balle.",
+            "WITCH": "Vous êtes la sorciere ! Votre objectif est d'éliminer tous les Loups Garous. Vous disposez de deux potions : une potion de vie pour sauver la victime des Loups et une potion de mort pour assassiner quelqu'un.",
+            "SEER": "Vous êtes la voyante ! Votre objectif est d'éliminer tous les Loups Garous. Chaque nuit, vous pouvez espionner un joueur et découvrir sa véritable identité.",
+            "GIRL": "Vous êtes la petite fille ! Votre objectif est d'éliminer tous les Loups Garous. Chaque nuit, vous pouvez espionner les loups et tenter de découvrir leurs identité."
+        },
+    }
+];
+
+const KILL_REASON = {
+    HUNTER: 0,
+    WOLF: 1,
+    LEFT: 2,
+    LOVE: 3,
+    VILLAGER: 4
+}
+
+function Player(lg, name) {
+    this.loupgarou = lg;
+    this.dead = null;
+    this.killReason = null;
+    this.role = null;
+    this.lover = null;
+    this.name = name;
+    this.reminder = null;
+    this.jobDone = null;
+}
+
+Player.prototype.rename = function(newNick) { this.name = newNick; };
+
+Player.prototype.kill = function(reason, msg) {
+    this.dead = reason;
+    this.killReason = msg;
+    this.loupgarou.onKilled(this);
+}
+
+LoupGarou.prototype.init = function(bot, chanName) {
+    this.room = chanName;
+    this.bot = bot;
+    this.users = {};
+    this.players = {};
+    this.stepListeners = {};
+    this.timeInStep = 0;
+    this.currentStep = STEP.NOT_STARTED;
+    this.currentScenario = langMng[0];
+    this.nightChannelPersons = {};
+    this.justDead = [];
+
+    for (var i in STEP) this.stepListeners[STEP[i]] = [];
+    this.stepListeners[STEP.NOT_STARTED].push(function() { this.bot.setModes(this.room, "-m"); this.bot.endGame(this); });
+    this.stepListeners[STEP.REGISTRATION].push(LoupGarou.prototype.onRegistration);
+    this.stepListeners[STEP.SET_ROLES].push(LoupGarou.prototype.startGame);
+    this.stepListeners[STEP.SELECT_CUPIDON].push(LoupGarou.prototype.onCupidon);
+    this.stepListeners[STEP.NIGHT].push(function() { this.bot.sendMsg(this.room, "C'est la nuit, crack crack boum"); this.nextStep(); }); // FIXME this.currentScenario....
+    this.stepListeners[STEP.NIGHT_SEER].push(LoupGarou.prototype.onSeer);
+    this.stepListeners[STEP.NIGHT_GIRL].push(LoupGarou.prototype.onGirl);
+    this.stepListeners[STEP.NIGHT_LG].push(LoupGarou.prototype.onWolf);
+    this.stepListeners[STEP.NIGHT_WITCH].push(LoupGarou.prototype.onWitch);
+    this.stepListeners[STEP.DAY_LOVER].push(LoupGarou.prototype.onLovers);
+    this.stepListeners[STEP.DAY_HUNTER].push(LoupGarou.prototype.onHunter);
+    this.stepListeners[STEP.DAY].push(LoupGarou.prototype.onDay);
+    this.stepListeners[STEP.DAY].push(LoupGarou.prototype.cleanCorpses);
+    this.stepListeners[STEP.DAY].push(LoupGarou.prototype.checkEndOfGame);
+    this.stepListeners[STEP.DAY_VILLA].push(LoupGarou.prototype.onVillagers);
+    this.stepListeners[STEP.DAY_EVENING].push(LoupGarou.prototype.onEvening);
+    this.stepListeners[STEP.DAY_EVENING].push(LoupGarou.prototype.checkEndOfGame);
+
+    var self = this;
+    this.bot.bot.addListener("pm", (from, text) => {
+        self.onPm(from, text);
+    });
+}
+
+LoupGarou.prototype.getName = function(){ return "loupgarou"; };
+LoupGarou.prototype.onActivate = function(){
+    this.currentScenario = langMng[Math.floor(Math.random() *langMng.length)];
+    this.setStep(STEP.REGISTRATION);
+    console.info("Starting Loup-garou");
+    this.bot.setModes(this.config.privateChannel, "+iKmnpt");
+    for (var i in this.nightChannelPersons)
+        this.bot.kick(this.config.privateChannel, i);
+};
+
+LoupGarou.prototype.onAddMode = function(user, mode) {};
+LoupGarou.prototype.onRemMode = function(user, mode) {};
+LoupGarou.prototype.onRename = function(oldNick, newNick) {
+    this.users[newNick.toLowerCase()] = this.users[oldNick.toLowerCase()];
+    delete this.users[oldNick.toLowerCase()];
+    var p = this.players[newNick.toLowerCase()] = this.players[oldNick.toLowerCase()];
+    delete this.players[oldNick.toLowerCase()];
+    if (this.nightChannelPersons[oldNick.toLowerCase()]) {
+        this.nightChannelPersons[newNick.toLowerCase()] = this.nightChannelPersons[oldNick.toLowerCase()];
+        delete this.nightChannelPersons[oldNick.toLowerCase()];
+    }
+    p && p.rename(newNick);
+};
+
+LoupGarou.prototype.onNickPart = function(nick) {
+    delete this.users[nick.toLowerCase()];
+    var p = this.players[nick.toLowerCase()];
+    if (p) {
+        if (this.currentStep > STEP.REGISTRATION) {
+            p.kill(KILL_REASON.LEFT, this.currentScenario.killedLeft(p.name));
+        } else {
+            this.bot.sendMsg(this.room, this.currentScenario.unregisterLeft(p.name));
+            delete this.players[nick.toLowerCase()];
+        }
+    }
+};
+
+LoupGarou.prototype.onNameList = function(nicks) {
+    this.users = {};
+    for (var i in nicks) {
+        var u = this.users[i.toLowerCase()] = (this.users[i.toLowerCase()] || this.bot.createUser(i));
+        u.setModeChar(nicks[i]);
+    }
+};
+
+LoupGarou.prototype.onSelfJoin = function() {
+    // Preparing night channel
+    var _this = this;
+    this.nightChannelPersons = this.nightChannelPersons || {};
+    this.bot.bot.addListener("join" +_this.config.privateChannel, nick => {
+        if (this.bot.name.toLowerCase() !== nick.toLowerCase())
+            _this.nightChannelPersons[nick.toLowerCase()] = true;
+    });
+    this.bot.bot.addListener("names" +_this.config.privateChannel, nicks => {
+        for (var nick in nicks)
+            if (this.bot.name.toLowerCase() !== nick.toLowerCase())
+                _this.nightChannelPersons[nick.toLowerCase()] = true;
+    });
+    this.bot.bot.addListener("part" +_this.config.privateChannel, nick => {
+        delete _this.nightChannelPersons[nick.toLowerCase()];
+    });
+    this.bot.bot.addListener("kick" +_this.config.privateChannel, nick => {
+        delete _this.nightChannelPersons[nick.toLowerCase()];
+    });
+    this.bot.bot.join(this.config.privateChannel, () => {
+        this.bot.setModes(_this.config.privateChannel, "+iKmnpt");
+    });
+    this.bot.setModes(this.room, "-m");
+};
+
+LoupGarou.prototype.onJoin = function(nick) {
+  this.users[nick.toLowerCase()] = this.users[nick.toLowerCase()] || this.bot.createUser(nick);
+};
+
+LoupGarou.prototype.delaySetStep = function() {
+    LoupGarou.prototype.setStep.delay = null;
+    this.setStep(LoupGarou.prototype.setStep.nextStep);
+};
+
+LoupGarou.prototype.setStep = function(newStep) {
+    if (LoupGarou.prototype.setStep.recursive === true) {
+        LoupGarou.prototype.setStep.nextStep = newStep;
+        if (!LoupGarou.prototype.setStep.delay)
+            LoupGarou.prototype.setStep.delay = setTimeout(LoupGarou.prototype.delaySetStep.bind(this), 250);
+        return;
+    }
+    LoupGarou.prototype.nextStep.messageDelayed = [];
+    LoupGarou.prototype.setStep.recursive = true;
+    this.timeInStep = Date.now();
+    this.clearReminder();
+    this.currentStep = newStep;
+    console.log("Entering step", this.currentStep, STEP);
+    console.log((this.stepListeners[newStep] ? this.stepListeners[newStep].length : 0) + "found listeners");
+    this.stepListeners[newStep] && this.stepListeners[newStep].some(i => i.call(this), this);
+    LoupGarou.prototype.setStep.recursive = false;
+};
+
+LoupGarou.prototype.nextStep = function() {
+    LoupGarou.prototype.nextStep.messageDelayed.forEach(i => this.bot.sendMsg(i[0], i[1]), this);
+    this.setStep(this.currentStep == MAX_STEP ? STEP.NIGHT : (this.currentStep +1));
+};
+
+LoupGarou.prototype.nextStep.messageDelayed = [];
+
+LoupGarou.prototype.onMessage = function(user, message){
+    switch (this.currentStep) {
+        case STEP.REGISTRATION:
+            if (message === "!loupgarou") {
+                if (this.players[user.toLowerCase()]) {
+                    this.bot.sendMsg(this.room, `${user}, tu es déjà inscrit au Loup-Garou`);
+                    this.bot.voice(this.room, user);
+                    return;
+                }
+                var p = this.players[user.toLowerCase()] = new Player(this, user);
+                this.bot.sendMsg(this.room, this.currentScenario.join(p.name));
+                this.bot.voice(this.room, p.name);
+            }
+    }
+};
+
+LoupGarou.prototype.devoiceAll = function() {
+    this.bot.devoice(this.room, Object.keys(this.users));
+}
+
+LoupGarou.prototype.voiceAll = function() {
+    var players = [];
+    for (var i in this.players)
+        this.players[i].dead === null && players.push(this.players[i].name);
+    this.bot.voice(this.room, players);
+}
+
+LoupGarou.prototype.clearReminder = function() {
+    if (this.currentReminder)
+        clearInterval(this.currentReminder);
+}
+
+LoupGarou.prototype.setReminder = function(cb) {
+    this.clearReminder();
+    if (this.config.reminderInterval > 0)
+        this.currentReminder = setInterval(cb.bind(this), this.config.reminderInterval * 1000);
+}
+
+LoupGarou.prototype.onRegistration = function() {
+    this.players = {};
+    this.bot.sendMsg(this.room, "Pour vous inscrire à la prochaine partie de Loup Garou, tapez !loupgarou !");
+    this.devoiceAll();
+    this.bot.setModes(this.room, "-m");
+    setTimeout((function() {this.setStep(STEP.SET_ROLES);}).bind(this), this.config.registrationDelay * 1000);
+    this.setReminder(function() {
+        var names = [];
+        for (var i in this.players) names.push(this.players[i].name);
+        this.bot.sendMsg(this.room, "Pour vous inscrire à la prochaine partie de Loup Garou avec " +names.join(", ") +", tapez !loupgarou ! Prochaine partie dans " +Math.round((this.config.registrationDelay -(Date.now() / 1000) +this.timeInStep)) +" secondes");
+    });
+};
+
+LoupGarou.prototype.doSetupRoles = function(roleList) {
+    roleList = roleList.sort(() => Math.random() -0.5);
+    var i =0;
+    for (var p in this.players) {
+        var player = this.players[p];
+        player.role = roleList[i];
+        this.bot.sendMsg(player.name, this.currentScenario.advRole[player.role]);
+        ++i;
+    }
+};
+
+LoupGarou.prototype.setupRoles = function() {
+    var nbPlayers = Object.keys(this.players).length;
+    if (!ROLE_REPARTITION.some(roles => {
+            if (roles.length === nbPlayers) {
+                this.doSetupRoles(roles);
+                return true;
+            }
+        }, this)) {
+        // Error: not enought players !
+        this.bot.sendMsg(this.room, "Il n'y a pas assez de personnes inscrites pour pouvoir jouer au Loup-Garou. Fin de la partie.");
+        this.devoiceAll();
+        console.info("Loup-Garou: not enought player");
+        this.setStep(STEP.NOT_STARTED);
+        return false;
+    }
+    return true;
+};
+
+LoupGarou.prototype.debug = function() {
+    var debug = {};
+    for (var i in this.players) debug[this.players[i].name] = {
+        dead: this.players[i].dead,
+        role: this.players[i].role
+    };
+    console.log(debug);
+};
+
+LoupGarou.prototype.startGame = function() {
+    // Setup roles
+    if (!this.setupRoles())
+        return;
+    this.bot.setModes(this.room, "+m");
+    this.devoiceAll();
+    this.debug();
+    // Invite wolfs to the private channel
+    this.bot.sendMsg(this.room, this.currentScenario.inviteWolfRoom());
+    for (var i in this.players) if (this.players[i].role === ROLES.WOLF) {
+        var p = this.players[i];
+        this.bot.sendMsg(p.name, this.currentScenario.inviteWolfRoomPrivate(this.config.privateChannel));
+        this.bot.invite(this.config.privateChannel, p.name);
+    }
+    this.nextStep(); // cupidon
+};
+
+LoupGarou.prototype.withRole = function(r) {
+    var result = [];
+    for (var i in this.players)
+        if (this.players[i].role === r && this.players[i].dead === null)
+            result.push(this.players[i]);
+    return result;
+};
+
+LoupGarou.prototype.setLovers = function(cupidon, p1, p2) {
+    if (this.currentStep !== STEP.SELECT_CUPIDON)
+        return;
+    p1.lover = p2;
+    p2.lover = p1;
+    this.bot.sendMsg(cupidon.name, this.currentScenario.setLoveWith(p1.name, p2.name));
+    if (p1 !== cupidon) LoupGarou.prototype.nextStep.messageDelayed.push([p1.name, this.currentScenario.inLoveWith(p2.name)]);
+    if (p2 !== cupidon) LoupGarou.prototype.nextStep.messageDelayed.push([p2.name, this.currentScenario.inLoveWith(p1.name)]);
+};
+
+LoupGarou.prototype.sendCupidonMsg = function (targets) {
+    var playerNames = [];
+    for (var i in this.players) playerNames.push(this.players[i].name);
+    var msg = this.currentScenario.cupidonMessage(playerNames);
+    console.log(targets, msg);
+    targets.forEach(i => this.bot.sendMsg(i.name, msg), this);
+};
+
+LoupGarou.prototype.onCupidonInternal = function(player, msg) {
+    msg = msg.split(" ");
+    if (msg.length === 2 && this.players[msg[0].trim().toLowerCase()] && this.players[msg[1].trim().toLowerCase()]) {
+        var p1 = this.players[msg[0].trim().toLowerCase()],
+            p2 = this.players[msg[1].trim().toLowerCase()];
+        if (p1 && p2) {
+            this.setLovers(player, p1, p2);
+            player.jobDone = true;
+            return;
+        }
+    }
+    this.sendCupidonMsg([player]);
+};
+
+LoupGarou.prototype.onCupidon = function() {
+    var players = this.withRole(ROLES.CUPIDON);
+    if (players.length) {
+        this.bot.sendMsg(this.room, this.currentScenario.waitingCupidon(this.config.cupidonDelay));
+        this.sendCupidonMsg(players);
+        setTimeout((function() {
+            players.forEach(p => p.jobDone === null && this.bot.sendMsg(p.name, this.currentScenario.setNoLove()), this);
+            this.nextStep();
+        }).bind(this), this.config.cupidonDelay * 1000);
+    }
+    else
+        this.nextStep();
+};
+
+LoupGarou.prototype.onSeerInternal = function(player) {
+    //FIXME ...
+    this.bot.sendMsg(this.room, "Attente voyante"); // FIXME this.currentScenario
+    this.nextStep();
+};
+
+LoupGarou.prototype.onSeer = function() {
+    var players = this.withRole(ROLES.SEER);
+    if (players.length) {
+        this.onSeerInternal(players[0]);
+    } else {
+        this.nextStep();
+    }
+};
+
+LoupGarou.prototype.onGirlInternal = function(player) {
+    //FIXME ...
+    this.bot.sendMsg(this.room, "Attente petite fille"); // FIXME this.currentScenario
+    this.nextStep();
+};
+
+LoupGarou.prototype.onGirl = function() {
+    var players = this.withRole(ROLES.GIRL);
+    if (players.length) {
+        this.onGirlInternal(players[0]);
+    } else {
+        this.nextStep();
+    }
+};
+
+LoupGarou.prototype.onWolfInternal = function(player) {
+    //FIXME ...
+    this.bot.sendMsg(this.room, "Attente loups"); // FIXME this.currentScenario
+    setTimeout((function() { this.nextStep(); }).bind(this), 15000);
+};
+
+LoupGarou.prototype.onWolf = function() {
+    var notWolf = [];
+    for (var i in this.players) if (this.players[i].role !== ROLES.WOLF) notWolf.push(this.players[i]);
+    notWolf = notWolf.sort(() => Math.random() -.5)[0];
+    notWolf.kill(KILL_REASON.WOLF, this.currentScenario.killedWolf(notWolf.name));
+
+    var players = this.withRole(ROLES.WOLF);
+    if (players.length) {
+        this.onWolfInternal(players[0]);
+    } else {
+        this.nextStep();
+    }
+};
+
+LoupGarou.prototype.onVillagers = function() {
+    var remainingPlayers = [];
+    for (var i in this.players)
+        if (this.players[i].dead === null)
+            remainingPlayers.push(this.players[i].name);
+    this.debug();
+
+    this.bot.sendMsg(this.room, this.currentScenario.startVotes());
+    this.bot.sendMsg(this.room, this.currentScenario.remainingPseudos(remainingPlayers.sort()));
+    this.voiceAll();
+    this.nextStep();
+};
+
+LoupGarou.prototype.onEvening = function() {
+    this.devoiceAll();
+    var anyone = [];
+    for (var i in this.players) anyone.push(this.players[i]);
+    anyone = anyone.sort(() => Math.random() -.5)[0];
+    anyone.kill(KILL_REASON.VILLAGER, this.currentScenario.killedVillager(anyone.name));
+};
+
+LoupGarou.prototype.onWitchInternal = function(player) {
+    //FIXME ...
+    this.bot.sendMsg(this.room, "Attente sorciere"); // FIXME this.currentScenario
+    this.nextStep();
+};
+
+LoupGarou.prototype.onWitch = function() {
+    var players = this.withRole(ROLES.WITCH);
+    if (players.length) {
+        this.onWitchInternal(players[0]);
+    } else {
+        this.nextStep();
+    }
+};
+
+LoupGarou.prototype.onLovers = function() {
+    for (var i =0, len = this.justDead.length; i < len; ++i)
+        if (this.justDead[i].lover)
+            this.justDead[i].lover.kill(KILL_REASON.LOVE, this.currentScenario.killedLove(this.justDead[i].lover.name, this.justDead[i].name));
+    this.nextStep();
+};
+
+LoupGarou.prototype.onHunter = function() {
+    for (var i =0, len = this.justDead.length; i < len; ++i)
+        if (this.justDead[i].role === ROLES.HUNTER)
+            this.bot.sendMsg(this.justDead[i].name, "!pan"); // FIXME this.currentScenario...
+    this.nextStep();
+};
+
+LoupGarou.prototype.cleanCorpses = function() {
+    this.justDead = [];
+};
+
+LoupGarou.prototype.checkEndOfGame = function() {
+    var alive = [];
+    for (var i in this.players) this.players[i].dead === null && alive.push(this.players[i]);
+    console.log("alive => ", alive);
+    if (alive.length === 2 && alive[0].lover === alive[1]) {
+        this.bot.sendMsg(this.room, this.currentScenario.loverWins());
+        this.bot.sendMsg(this.room, this.currentScenario.congrats(alive.map(i => i.name)));
+        this.setStep(STEP.NOT_STARTED);
+        return true;
+    }
+    var liveWolfCount = alive.filter(i => i.role === ROLES.WOLF).length;
+    if (liveWolfCount === alive.length) {
+        this.bot.sendMsg(this.room, this.currentScenario.wolfWins());
+        this.bot.sendMsg(this.room, this.currentScenario.congrats(alive.map(i => i.name)));
+        this.setStep(STEP.NOT_STARTED);
+        return true;
+    }
+    if(!liveWolfCount) {
+        this.bot.sendMsg(this.room, this.currentScenario.villagerWins());
+        this.bot.sendMsg(this.room, this.currentScenario.congrats(alive.map(i => i.name)));
+        this.setStep(STEP.NOT_STARTED);
+        return true;
+    }
+    this.nextStep();
+};
+
+LoupGarou.prototype.advertiseDeath = function(player) {
+    this.bot.sendMsg(this.room, player.killReason +" " +this.currentScenario.whowaswhat[player.role](player.name));
+};
+
+LoupGarou.prototype.onKilled = function(player) {
+    if (this.currentStep < STEP.DAY)
+        this.justDead.push(player);
+    else
+        this.advertiseDeath(player);
+};
+
+LoupGarou.prototype.onDay = function() {
+    if (this.justDead.length === 0)
+        this.bot.sendMsg(this.room, this.currentScenario.dayNoDead());
+    else if (this.justDead.length === 1)
+        this.bot.sendMsg(this.room, this.currentScenario.dayOneDead());
+    else
+        this.bot.sendMsg(this.room, this.currentScenario.dayMultiDead());
+    this.justDead.forEach(i => this.advertiseDeath(i), this);
+};
+
+LoupGarou.prototype.onPm = function(from, text) {
+    if (this.currentStep === STEP.NOT_STARTED)
+        return;
+    var player = this.players[from.toLowerCase()];
+    if (!player)
+        return;
+    if (this.currentStep === STEP.SELECT_CUPIDON && player.role === ROLES.CUPIDON && player.jobDone === null) {
+        this.onCupidonInternal(player, text);
+    }
+};
+
+module.exports = LoupGarou;
+

+ 11 - 8
quizz.js

@@ -81,7 +81,7 @@ Question.toHint = function(response, normalizedResponse, boundaries, responseInd
         return "Un nombre entre " +boundaries[0] +" et " +boundaries[1];
     }
     else
-        console.error("Unknown response type !");
+        console.error("[quizz] Unknown response type !");
 };
 
 Question.prototype.getHint = function(hintLevel) {
@@ -160,7 +160,7 @@ const ScoreUtils = (function() {
 
 function initQuestionList(filename) {
     return new Promise((ok, ko) => {
-        console.log("Reloading question db");
+        console.log("[quizz] Reloading question db");
         var stream = fs.createReadStream(filename),
             reader = readline.createInterface({input: stream}),
             questions = [],
@@ -178,7 +178,7 @@ function initQuestionList(filename) {
                 if (question.question && question.response)
                     questions.push(question);
             } catch (e) {
-                console.error("Failed to load Database: ", e, "on line", lineNo);
+                console.error("[quizz] Failed to load Database: ", e, "on line", lineNo);
                 borken = true;
                 reader.close();
                 stream.destroy();
@@ -213,6 +213,9 @@ QuizzBot.prototype.init = function(bot, chanName) {
     this.mySQLExportWrapper();
 }
 
+QuizzBot.prototype.onActivate = function(){}
+QuizzBot.prototype.getName = function(){ return "quizz"; }
+
 QuizzBot.prototype.onSelfJoin = function() {
     this.init = true;
     this.reloadDb();
@@ -307,7 +310,7 @@ QuizzBot.prototype.nextQuestionWrapper = function() {
 
 QuizzBot.prototype.nextQuestion = function() {
     this.currentQuestion = this.questions[Math.floor(Math.random() * this.questions.length)];
-    console.log(this.currentQuestion);
+    console.log("[quizz]", this.currentQuestion);
     this.currentHint = 0;
     this.questionDate = Date.now();
     this.bot.sendMsg(this.room, "#" +this.currentQuestion.id +" " +this.currentQuestion.question);
@@ -334,7 +337,7 @@ QuizzBot.prototype.reloadDb = function() {
         _this.bot.sendMsg(this.room, questions.length +" questions loaded from database");
         _this.start();
     }).catch(err => {
-        console.error(err);
+        console.error("[quizz]", err);
         _this.bot.sendMsg(this.room, err);
     });
 }
@@ -626,15 +629,15 @@ QuizzBot.prototype.mySQLExport = function() {
     this.exportScores().then(() => {
         Cache.setExportTs();
         this.resetScores();
-        console.log("Successfully exported scores to MySQL");
+        console.log("[quizz] Successfully exported scores to MySQL");
         this.mySQLExportWrapper();
     }).catch((errString) => {
-        console.error("mySQL Export error saving to database: ", errString);
+        console.error("[quizz] mySQL Export error saving to database: ", errString);
     });
 }
 
 QuizzBot.prototype.exportScores = function() {
-    console.log("Start exporting scores");
+    console.log("[quizz] Start exporting scores");
     return new Promise((ok, ko) => {
         if (!MySQL)
             return ko();

+ 11 - 13
rapido.js

@@ -7,7 +7,7 @@ Object.assign(global, require("./config.js"));
 
 function initWordList(filename) {
     return new Promise((ok, ko) => {
-        console.log("Reloading dictionnary");
+        console.log("[rapido] Reloading dictionnary");
         var stream = fs.createReadStream(filename),
             reader = readline.createInterface({input: stream}),
             words = [];
@@ -37,6 +37,9 @@ Rapido.prototype.init = function(bot, chanName) {
     this.resetScoresWrapper();
 }
 
+Rapido.prototype.getName = function() { return "rapido"; }
+Rapido.prototype.onActivate = function() { };
+
 Rapido.prototype.onSelfJoin = function() {
     this.init = true;
     this.reloadDb();
@@ -67,10 +70,10 @@ Rapido.prototype.reloadDb = function() {
     initWordList(this.config.DICTIONARY_PATH).then(words => {
         _this.reloading = false;
         _this.words = words;
-        _this.bot.sendMsg(this.room, words.length +" words loaded from database");
+        console.info("[rapido] " +words.length +" words loaded from database");
         _this.endWord();
     }).catch(err => {
-        console.error(err);
+        console.error("[rapido]", err);
         _this.bot.sendMsg(this.room, err);
     });
 }
@@ -128,8 +131,10 @@ Rapido.prototype.onWordTimeout = function() {
         this.bot.sendMsg(this.room, "Bah alors ? " +this.wordRequest.join(", ") +" vous fichez quoi ?");
     }
     this.endWord();
-    if (this.setProgression >= this.config.WORD_IN_SET)
+    if (this.setProgression >= this.config.WORD_IN_SET) {
         this.bot.sendMsg(this.room, "fin du temps réglementaire, tapez !rapido pour une prochaine partie.");
+        this.bot.endGame(this);
+    }
     else
         this.startNextWordTimer();
 }
@@ -142,7 +147,7 @@ Rapido.prototype.startNewSet = function() {
 Rapido.prototype.startNextWordTimer = function() {
     var _this = this;
     this.currentWord = this.words[Math.floor(Math.random() * this.words.length)];
-    console.log(this.currentWord);
+    console.log(`[rapido] ${this.currentWord}`);
     _this.bot.sendMsg(this.room, "Attention attention, prochain mot dans " +Math.floor(this.config.NEXT_WORD_DELAY / 1000) +" secondes");
     if (this.setProgression == 0)
         _this.bot.sendMsg(this.room, "Rappel : il faut taper le plus vite possible en minuscule et sans espaces le mot proposé");
@@ -177,11 +182,6 @@ Rapido.prototype.sendScore = function() {
 
 Rapido.prototype.computeScore = function(ellapsed, word, index) {
     const fps = 1000 * word.length / ellapsed;
-    console.log({
-        fps: fps,
-        ellapsed: ellapsed,
-        word: word
-    });
     var base = 2 +(index == 0 ? 2 : 0);
     for (var i =0, len = this.config.SCORE_MAP.length; i < len; ++i) {
         var scoreItem = this.config.SCORE_MAP[i];
@@ -195,9 +195,7 @@ Rapido.prototype.onMessageInternal = function(username, user, msg) {
     const lmsg = msg.toLowerCase();
     msg = lmsg.substr(0, 1) + msg.substr(1);
 
-    if (lmsg === "!next") {
-        this.bot.sendMsg(this.room, "La commande !next a été remplacé par !rapido.");
-    } else if (lmsg === "!rapido") {
+    if (lmsg === "!rapido") {
         if (this.wordRequest.indexOf(username) === -1)
             this.wordRequest.push(username);
         if (!this.currentWord && this.wordRequest.length >= this.config.MIN_PLAYERS)