【技能开发分享】记扑克牌


(illxi) #1

大家好,我是Sam。这里给大家简单介绍一下 记扑克牌这个Skill。

玩法:

根据技能内提示开始游戏后, 若琪把扑克牌分成n行(默认是4行),每行5张。然后逐行读出。
您要做的是像最强大脑选手那样,把所有扑克牌的点数、花色和顺序都牢牢记住。
然后,若琪会抽查你的记忆。比如会问:
“黑桃2是第几行第几张?”
请根据记忆回答,若琪会告诉你对错。
到最后若琪也会给出你回答对和错的总次数。

游戏流程图

要啥自行车啊

语音交互设计

意图定义
{
	"intents": [
		{
			"intent": "cards",
			"slots": [
				{
					"name": "s",
					"type": "suit"
				},
				{
					"name": "v",
					"type": "value"
				}
			],
			"user_says": [
				"$s$v"
			]
		},
		{
			"intent": "reheard_questions",
			"slots": [
				{
					"name": "ques",
					"type": "re_question"
				}
			],
			"user_says": [
				"再(听|读|说|问|讲)一次$ques",
				"什么",
				"再(听|读|说|问|讲)一次"
			]
		},
		{
			"intent": "player_answer",
			"slots": [
				{
					"name": "numberRow",
					"type": "ROKID.NUMBER_ZH"
				},
				{
					"name": "numberColumn",
					"type": "ROKID.NUMBER_ZH"
				}
			],
			"user_says": [
				"第$numberRow(行|排|横)(的)?第$numberColumn(张|个|位|章)"
			]
		},
		{
			"intent": "player_ready",
			"slots": [],
			"user_says": [
				"准备(好了|完了|完毕|结束)",
				"可以(开始)?了"
			]
		},
		{
			"intent": "reheard_cards",
			"slots": [
				{
					"name": "cards",
					"type": "re_cards"
				}
			],
			"user_says": [
				"再(听|读|说|问|讲)一次$cards"
			]
		},
		{
			"intent": "game_difficulty",
			"slots": [
				{
					"name": "HowManyRow",
					"type": "ROKID.NUMBER_ZH"
				}
			],
			"user_says": [
				"我要记$HowManyRow(行|排|横)"
			]
		},
		{
			"intent": "game_start",
			"slots": [],
			"user_says": [
				"重新开始",
				"开始(新)?游戏",
				"重来",
				"启动游戏",
				"(再)?(来|玩|开)一(盘|次|局|场)",
				"游戏开始"
			]
		}
	]
}

配置 - RFS

技能的服务端配置我选择的是js-engine模式。这个模式非常好,不需要我自己去搭建云服务器,只需要用js的方式写出自己的业务逻辑即可。

直接上代码吧, 希望抛砖引玉。

JS Engine代码
const TEXT_WELCOME = "欢迎进入记牌游戏,要开始新游戏,请对若琪说,开始游戏。";
const RESUME_GAME_TTS = "您好像有未完成的游戏,已为您读取。如要开始新的游戏,请对若琪说,开始游戏。您之前的问题是,%s是第几行的第几张?";
const START_GAME_TTS = "<speak>好的,您将要记忆%s张牌,分%s航读出,每航五张。下面请记牌。<break time=\"1s\"/> %s。<break time=\"1s\"/>读牌完毕。请整理记忆,准备好答题后,请对若琪说,准备好了。如果要再听一次,可以对若琪说“再听一次。”</speak>";
const reheard_TTS = "<speak>好的,您将要记忆%s张牌,分%s行读出,每航五张。下面请记牌。<break time=\"1s\"/> %s。<break time=\"1s\"/>读牌完毕。</speak>";
const reheard_question_TTS = "请问,%s是第几行的第几张?";
const QUESTION_TTS = "请问,%s是第几行第几张?";
const NEXTQUESTION_TTS = "下一题,请问,%s是第几行第几张?";
const ENDGAME_TTS = "游戏结束。您记对了%s张牌,记错了%s张牌。再玩一次,请对若琪说“开始游戏”。要退出,请对若琪说:“关闭记牌游戏”。";
const SETDIFFICULTY_TTS = "成功把难度设置为%s航,下一盘游戏将会使用新难度。直接开始新游戏请对我说“开始新游戏”。";
const CORRECT_TTS = "正确。";
const WRONG_TTS = "错误。";
const NUM_ROW_TTS = "第%s行:";
const ERROR1_TTS = "您是不是还没有开始游戏?请对若琪说“开始游戏”。";
const ERROR2_TTS = "别急,还没出题呢。要继续游戏,请对若琪说“准备好了”。如果要再听一次题面,可以对若琪说“再听一次牌。";
const ERROR3_TTS = "游戏已经开始了,请回答问题,如果没有听清问题,请对若琪说“再问一次问题”。";
const SETDIFFICULTY_ERROR = "对不起,难度只能在一到十之间选择。";
var code2tts = {"AS":"黑桃Ace","AH":"红心Ace","AC":"草花Ace","AD":"方片Ace",
                "2S":"黑桃2","2H":"红心2","2C":"草花2","2D":"方片2",
                "3S":"黑桃3","3H":"红心3","3C":"草花3","3D":"方片3",
                "4S":"黑桃4","4H":"红心4","4C":"草花4","4D":"方片4",
                "5S":"黑桃5","5H":"红心5","5C":"草花5","5D":"方片5",
                "6S":"黑桃6","6H":"红心6","6C":"草花6","6D":"方片6",
                "7S":"黑桃7","7H":"红心7","7C":"草花7","7D":"方片7",
                "8S":"黑桃8","8H":"红心8","8C":"草花8","8D":"方片8",
                "9S":"黑桃9","9H":"红心9","9C":"草花9","9D":"方片9",
                "0S":"黑桃十","0H":"红心十","0C":"草花十","0D":"方片十",
                "JS":"黑桃Jay","JH":"红心Jay","JC":"草花Jay","JD":"方片Jay",
                "QS":"黑桃cue","QH":"红心cue","QC":"草花cue","QD":"方片cue",
                "KS":"黑桃Kay","KH":"红心Kay","KC":"草花Kay","KD":"方片Kay"};

function sprintf(template, values) {
    return template.replace(/%s/g, function() {
      return values.shift();
    });
  }

var cnNum2ArabNum = function(cn){
    //把中文数字转成阿拉伯数字
    let arab, parts, cnChars = '零一二三四五六七八九';
    if (!cn) {
        return 0;
    }
    if (cn.indexOf('亿') !== -1){
        parts = cn.split('亿');
        return cnNum2ArabNum(parts[0]) * 1e8 + cnNum2ArabNum(parts[1]);
    }
    if (cn.indexOf('万') !== -1){
        parts = cn.split('万');
        return cnNum2ArabNum(parts[0]) * 1e4 + cnNum2ArabNum(parts[1]);
    }
    if (cn.indexOf('十') === 0){
        cn = '一' + cn;
    }
    arab = cn
        .replace(/[零一二三四五六七八九]/g, function (a) {
            return '+' + cnChars.indexOf(a);
        })
        .replace(/(十|百|千)/g, function(a, b){
            return '*' + (
                b == '十' ? 1e1 :
                b == '百' ? 1e2 : 1e3
            );
        });
    return (new Function('return ' + arab))();
};


function shuffle(array) {
    //使用Fisher-Yates Shuffle打乱array的顺序
    let counter = array.length;
    while (counter > 0) {
        let index = Math.floor(Math.random() * counter);
        counter--;
        let temp = array[counter];
        array[counter] = array[index];
        array[index] = temp;
    }
    return array;
};

function pickaCard(phase) {
    //出题。
    let cardMatrix = JSON.parse(Rokid.param.session.attributes.sessionKey.cards);
    let picked_row = Math.floor(Math.random()*cardMatrix.length);
    let picked_column = Math.floor(Math.random()*cardMatrix[picked_row].length);
    let picked_card = cardMatrix[picked_row][picked_column];
    
    Rokid.param.session.attributes.sessionKey.question_asked += 1;
    Rokid.param.session.attributes.sessionKey.answerRow = picked_row;
    Rokid.param.session.attributes.sessionKey.answerColumn = picked_column;
    Rokid.param.session.attributes.sessionKey.phase = phase;
    Rokid.param.session.attributes.sessionKey.question_card = code2tts[picked_card];
    return picked_card
};


exports.handler = function(event, context, callback) {
    var rokid = Rokid.handler(event, context,callback);
    rokid.registerHandlers(handlers);
    rokid.execute();
};

var handlers = {
    'ROKID.INTENT.WELCOME':function() {
        //进入技能
        console.log("Welcome to the game.");
        Rokid.dbServer.get(Rokid.param.context.user.userId, (err, res) => {
            //查询是否有游戏存存档
            if(err) {
                this.emit(':error', err);
            }else{
                if (res === "未查询到相关数据") {
                    //无存档开始新游戏
                    attr = {'sessionKey': {"difficulty":4,"lines":4}};                   
                    this.emit('send_tts', TEXT_WELCOME, attr);
                } else {
                    //有存档,提示可以继续游戏
                    res = JSON.parse(res);
                    var qcard = res.sessionKey.question_card;
                    var tts = sprintf(RESUME_GAME_TTS, [qcard]);
                    this.emit('send_tts', tts, res)
                }
            }
        });
    },

    'game_start': function () {
        //游戏初始化
        Rokid.dbServer.delete(Rokid.param.context.user.userId, (err, res) => {
            //删除旧存档
            if(err) {
                this.emit(':error', err);
            }else{
                //设定游戏难度
                //游戏会根据lines的值生成相应难度的游戏
                //difficulty是用户可以自定义的游戏难度
                Rokid.param.session.attributes.sessionKey.lines = Rokid.param.session.attributes.sessionKey.difficulty;
                //开一副新牌
                let keys = Object.keys(code2tts);
                //洗牌
                randomCards = shuffle(keys);
                //切牌
                randomCards = randomCards.slice(0, Rokid.param.session.attributes.sessionKey.lines*5);
                //分牌
                let cardMatrix = randomCards.reduce(
                    (rows, key, index) => (index % 5 === 0 ? rows.push([key]) 
                : rows[rows.length-1].push(key)) && rows, []);

                //生成题目tts
                let CARD_ARRAY_TTS = "";
                for(var i = 0; i < Rokid.param.session.attributes.sessionKey.lines; i++) {
                    let cardRow = cardMatrix[i];
                    let rownum = i+1;
                    CARD_ARRAY_TTS = CARD_ARRAY_TTS + sprintf(NUM_ROW_TTS, [rownum]);
                    for(var j = 0; j < cardRow.length; j++) {
                        CARD_ARRAY_TTS = CARD_ARRAY_TTS + code2tts[cardRow[j]]+"。";
                    }
                }
                let tts = sprintf(START_GAME_TTS, [
                    Rokid.param.session.attributes.sessionKey.lines*5, 
                    Rokid.param.session.attributes.sessionKey.lines, CARD_ARRAY_TTS])

                //生成attributes
                //记录游戏进度phase = 1 完成start_game
                let cardsString = JSON.stringify(cardMatrix);
                let attributes = {"phase":1,"lines":4,"difficulty":4,"question_total":4,
                "question_asked":0,"question_card":"","corrects":0,"mistakes":0,"answerRow": 0,
                "answerColumn": 0,"cards": ""};
                attributes.difficulty = Rokid.param.session.attributes.sessionKey.difficulty;
                attributes.lines = Rokid.param.session.attributes.sessionKey.lines;
                attributes.cards = cardsString;
                let attr = {'sessionKey':attributes};
                this.emit('send_tts', tts, attr);
            }
        });
    },

    'game_difficulty': function () {
        //自定义难度
        //可以选择难度(记多少行的牌)
        let gameDifficulty = Rokid.param.request.content.slots.HowManyRow;
        gameDifficulty = cnNum2ArabNum(gameDifficulty);
        if (0 < gameDifficulty && gameDifficulty <= 10) {
            //不能超过一副牌
            Rokid.param.session.attributes.sessionKey.difficulty = gameDifficulty;
            let tts = sprintf(SETDIFFICULTY_TTS, [gameDifficulty]);
        } else {
            let tts = SETDIFFICULTY_ERROR;
        }
        this.emit('send_tts', tts, Rokid.param.session.attributes)
    },


    'player_ready':function() {
        //出题完毕,等待玩家整理记忆。准备出题。
        
        //检查是否有错误
        if (typeof Rokid.param.session.attributes.sessionKey.phase == "undefined" ||
            Rokid.param.session.attributes.sessionKey.phase < 1) {
            //报错,原因:没有开始游戏
            this.emit('error_GameNotStartYet')
        }

        if (Rokid.param.session.attributes.sessionKey.phase > 1) {
            //报错,原因:已经开始出题了。
            this.emit('send_tts', ERROR3_TTS, Rokid.param.session.attributes)
        }
        
        //提取题面,并出题。
        picked_card = pickaCard(2);
        let tts = sprintf(QUESTION_TTS, [code2tts[picked_card]]);
        let saveData = JSON.stringify(Rokid.param.session.attributes);
        
        //存档
        this.emit('save_game', tts, saveData);
    },

    'player_answer':function() {
        //玩家回答 第N张 --> 判断是否正确 --> 是否继续抽查 --> 继续提问/结束游戏
        if (Rokid.param.session.attributes.sessionKey.phase >= 2) {
            //判断phase已进入答题环节 
            let numberRow = Rokid.param.request.content.slots.numberRow;
            let numberColumn = Rokid.param.request.content.slots.numberColumn;
            numberRow = cnNum2ArabNum(numberRow);
            numberColumn = cnNum2ArabNum(numberColumn);
            
            //提取正确答案
            let answerRow = Rokid.param.session.attributes.sessionKey.answerRow;
            let answerColumn = Rokid.param.session.attributes.sessionKey.answerColumn;

            let tts = "";
            //判断是否回答正确
            if (numberRow == answerRow+1 && numberColumn == answerColumn+1){
                Rokid.param.session.attributes.sessionKey.corrects += 1;
                tts = CORRECT_TTS;
            } else {
                Rokid.param.session.attributes.sessionKey.mistakes += 1;
                tts = WRONG_TTS;
            }
            //判断是否继续提问 or 结束游戏
            if (Rokid.param.session.attributes.sessionKey.question_asked < 
                Rokid.param.session.attributes.sessionKey.question_total) 
            {
                //继续提问
                picked_card = pickaCard(3)
                tts = tts + sprintf(NEXTQUESTION_TTS, [code2tts[picked_card]]);
                let saveData = JSON.stringify(Rokid.param.session.attributes);
                //存档
                this.emit('save_game', tts, saveData);

            } else {
                //游戏结束

                //计算回答成绩
                if (Rokid.param.session.attributes.sessionKey.corrects == 2) {
                    var corrects_count = "两";
                } else {
                    var corrects_count = Rokid.param.session.attributes.sessionKey.corrects;
                }
                if (Rokid.param.session.attributes.sessionKey.mistakes == 2) {
                    var mistakes_count = "两";
                } else {
                    var mistakes_count = Rokid.param.session.attributes.sessionKey.mistakes;
                }
                //生成tts
                tts = tts + sprintf(ENDGAME_TTS, [corrects_count, mistakes_count]);

                //删除存档
                Rokid.dbServer.delete(Rokid.param.context.user.userId, (err, res) => {
                    if(err) {
                        this.emit(':error', err);
                        return;
                    }else{
                        //重置attributes
                        let attr = {'sessionKey': {"lines": Rokid.param.session.attributes.sessionKey.lines}}
                        this.emit('send_tts', tts, attr)
                    }
                });
            }
        }
        else {
            if (Rokid.param.session.attributes.sessionKey.phase == 1) {
                //错误,还没有出题,你在回答什么?
                this.emit('error_QuestionNotAskYet')
            } else {
                this.emit('error_GameNotStartYet')
                
            }
        }
    },

    'reheard_cards':function() {
        //重听一次牌
        try {
            if (typeof Rokid.param.session.attributes.sessionKey.phase == "undefined" ||
                Rokid.param.session.attributes.sessionKey.phase < 1) {
                //没有开始游戏
                this.emit('error_GameNotStartYet')
            }
        }
        catch (err) {
            this.emit(':error', err);
        }
        let CARD_ARRAY_TTS = "";
        let cardMatrix = JSON.parse(Rokid.param.session.attributes.sessionKey.cards);
        for(var i = 0; i < cardMatrix.length; i++) {
            let cardRow = cardMatrix[i];
            let rownum = i+1;
            CARD_ARRAY_TTS = CARD_ARRAY_TTS + sprintf(NUM_ROW_TTS, [rownum]);
            for(var j = 0; j < cardRow.length; j++) {
                CARD_ARRAY_TTS = CARD_ARRAY_TTS + code2tts[cardRow[j]]+",";
            }
        }
        let tts = sprintf(reheard_TTS, [cardMatrix.length*5, cardMatrix.length, CARD_ARRAY_TTS]);
        this.emit('send_tts', tts, Rokid.param.session.attributes)
    },

     'reheard_questions':function() {
        //重听一次问题
        try {
            if (typeof Rokid.param.session.attributes.sessionKey.phase == "undefined" ||
                Rokid.param.session.attributes.sessionKey.phase < 1) {
                //没有开始游戏
                this.emit('error_GameNotStartYet')
            } else if (Rokid.param.session.attributes.sessionKey.phase == 1) {
                //还未出题,没办法重复问题。
                this.emit('error_QuestionNotAskYet')
            }
        }
        catch (err) {
            this.emit(':error', err);
        }
        let qcard = Rokid.param.session.attributes.sessionKey.question_card;
        let tts = sprintf(reheard_question_TTS, [qcard]);
        this.emit('send_tts', tts, Rokid.param.session.attributes)

    },
   
    'error_GameNotStartYet':function () {
        //ERROR 1
        this.setTts({
            disableEvent: true,
            tts:ERROR1_TTS
        });
        if (typeof Rokid.param.session.attributes.sessionKey.lines == "undefined") {
            this.setSession({
                'sessionKey': {
                    "lines": 4
                }
            });
        } else {
            this.setSession(Rokid.param.session.attributes);
        }
        this.setPickup({enable: true, durationInMilliseconds: 6000});
        this.emit(':done');
    },

    'error_QuestionNotAskYet':function() {
        this.emit('send_tts', ERROR2_TTS, Rokid.param.session.attributes)
    },

    'send_tts':function(tts, attr){
        this.setTts({
            disableEvent: true,
            tts: tts
        });
        this.setSession(attr);
        this.setPickup({enable: true, durationInMilliseconds: 6000});
        this.emit(':done');
    },

    'save_game': function(tts, saveData) {
        //存档
        Rokid.dbServer.set(Rokid.param.context.user.userId, saveData, (err, res) => {
            if(err) {
                this.emit(':error', err);
            }else{
                this.emit('send_tts', tts, Rokid.param.session.attributes);
            }
        });
    }
};

(chao.xuc) #2

sam威武啊


(jun.lukesmail) #3

sam牛逼,希望有更多的人和sam一样玩起来,一起嗨


(阿莫斯疙瘩) #4

厉害


(大发) #5

赞赞赞偷师了,今天必须要开发一个skill