HTML+JS+websocket 实现联机“游戏王”对战(六)- 卡片放置,战场更新


目录:

游戏王联机卡牌对战 1 - 前言
游戏王联机卡牌对战 2 - 联机模式
游戏王联机卡牌对战 3 - 界面布局
游戏王联机卡牌对战 4 - 卡组系统
游戏王联机卡牌对战 5 - 卡片选中系统
游戏王联机卡牌对战 6 - 卡片放置,战场更新
游戏王联机卡牌对战 7 - 墓地,副控制面板
游戏王联机卡牌对战 8 - 返回手卡,牌组
游戏王联机卡牌对战 9 - 实现简单websocket通信
游戏王联机卡牌对战10 - 搭建游戏服务端
游戏王联机卡牌对战11 - 客户端消息的收发
游戏王联机卡牌对战12 - 消息发送具体场景
游戏王联机卡牌对战13 - 实机演示

功能按键的实现(一)

1. 卡片放置:

卡片的攻击,防御,背盖防御召唤以及发动,覆盖,这些操作都可归类为玩家向战场上放置卡片,卡片放置函数可通过传递不同参数来分别执行这些功能。

实现卡片放置主要有几个步骤:

(1)选中手牌某一张卡片,并记录卡片信息:
这个操作由我们前面章节介绍的卡片选中系统来完成,当我们选中某张卡片后会用一个全局对象记录卡片的来源类型(手牌/场上),卡片序号,卡牌图片 url 等信息供其他函数使用。

(2)寻找场上的空卡槽:
确定怪兽/魔法陷阱区域的某个空卡槽,如果没有空卡槽剩余则无法执行召唤/发动/覆盖。

(3)将被选中的手牌删除:
被选中的手牌被打到场上去了,故需从手牌被删除。

(4)更新战场信息并更新战场:
将被选中的卡片按要求加载至战场上并记录其状态。


卡片放置函数 placeCard:

参数含义
placetype放置类型,包括 攻击 / 防御 / 背盖防御 / 发动 / 覆盖
cardtype卡片类型,包括 怪兽 / 魔法陷阱
寻找 怪兽/魔法陷阱 区域的空卡槽;

如果(卡槽未满);
	如果(被选中的卡片来源于手牌):
		记录手牌id;
		通过手牌id获取手牌卡槽对象;
		清除手牌卡槽的卡片;
		
		更新战场数组fieldArrayPly1的内容;
		更新战场上的卡片;

		清空所有卡槽的选中状态;

/**
 * 我方从手牌向场上放置卡片,并发出放置指令 (攻击,防御,背盖防御,发动,盖卡)
 * @param {string} placetype - place type (attack/defence/back/on/off)
 * @param {string} cardtype - card type (monster/magic)
 */
function placeCard(placetype, cardtype) {

    var cardslot = findEmptySlot(cardtype) //寻找空的卡槽
    var cardsrc;

    if(cardslot == -1) {
        alert("卡槽已满");
    } else {
        if (SelectedCard.type == 'hand') {  //放置卡片必须来源于手牌
            /*获取被选中手卡信息 */
            var handslot = (SelectedCard.cardNo).toString();
            var handID = "p1-hand" + handslot;
            element = document.getElementById(handID);
            cardsrc = SelectedCard.cardSrc;
            element.src = "";  //手牌该卡消失

            /*更新战场信息 */
            fieldArrayPly1.FieldCards[cardslot].imgsrc = cardsrc;
            fieldArrayPly1.FieldCards[cardslot].state = placetype;

            /*发出指令,执行更新战场卡片的函数 */
            var fieldID = "p1-field" + cardslot.toString();
            updateField(fieldID, placetype, cardsrc);

            /**
             * 放置后告知对手执行战场更新函数;
             * 放置完成后记得告诉对手哪张手卡消失了;
             * 注意:我方战场变化对对方来说是P2;
             */
            var updateID = "p2-field" + cardslot.toString();
            messageField(placetype, updateID, cardsrc);
            messageHand('reduce', handslot);

            /*清空所有选中状态 */
            cleanSelected();
        }
    }
}

在html中:

<button class="button" type="button" name="attkSummon" onclick="placeCard('attk', 'monster')">攻击召唤</button>
<button class="button" type="button" name="defenSummon" onclick="placeCard('defen', 'monster')">守备召唤</button>
<button class="button" type="button" name="backSummon" onclick="placeCard('back', 'monster')">背盖召唤</button>
<button class="button" type="button" name="launchCard" onclick="placeCard('on', 'magic')">发动(手卡)</button>
<button class="button" type="button" name="coverCard" onclick="placeCard('off', 'magic')">覆盖(手卡)</button>

这些 button 都会调用 placeCard 函数,且根据不同的功能传入不同的参数。


寻找空卡槽函数 findEmptySlot

/**
 * 返回当前我方场上/手牌的空卡槽序号(怪兽卡槽与魔法陷阱卡槽也要区分开)
 * @param {string} slottype - type of wanted empty slot (monster/magic/hand) 
 */
function findEmptySlot(slottype) {
    var emptySlot = -1;

    if (slottype == 'monster') {  //放置怪兽卡搜索0-4卡槽
        for (var i=0; i<5; i++) {
            if (fieldArrayPly1.FieldCards[i].state == "null") {
                emptySlot = i;
                break;
            }
        }
    } else if (slottype == 'magic') {  //放置魔法陷阱卡搜索5-9卡槽
        for (var i=5; i<10; i++) {
            if (fieldArrayPly1.FieldCards[i].state == "null") {
                emptySlot = i;
                break;
            }
        }
    } else if (slottype == 'hand') {
        for (var i=0; i<8; i++) {
            var handID = 'p1-hand' + i.toString();
            element = document.getElementById(handID);
            if (element.src == emptysrc) {  //如果该卡槽为空
              emptySlot = i;
              break;
            }
        }
    }

    return emptySlot;
}

根据被选中卡片的类型去寻找相应区域的空卡槽,并返回卡槽序号。


2. 更变卡片表示形式:

卡片表示形式的更变包括怪兽卡与魔法陷阱卡。

怪兽卡的形式变更顺序:
攻击 -> 防御 -> 背盖 -> 攻击 -> …

魔法陷阱的形式更变顺序:
覆盖 -> 表侧 -> 覆盖 -> …


更变形式函数 changeState

参数含义
cardtype卡片类型,包括 怪兽/魔法陷阱
如果(被选中的卡属于场上 且 属于我方玩家player1):
	获取卡槽id;
	获取卡槽中的卡片url以及当前状态(即表示形式state);
	
	如果卡片类型是:
	怪兽:
		按 攻击/防御/背盖 的顺序变更1次状态并记录;
	魔法陷阱:
		按 表侧/背盖 的顺序变更1次状态并记录;
	
	更新战场数组fieldArrayPly1中该卡槽的状态(state);
	更新战场上的卡片;

/**
 * 更变卡片的表示形式
 * 更变顺序为:攻击 -> 防御 -> 背盖 -> 攻击, 盖覆卡 -> 表侧卡 -> 盖覆卡
 * @param {string} cardtype - card type (monster/magic)
 */
function changeState(cardtype) {
    if (SelectedCard.type == 'field' && SelectedCard.player == 'player1') {  //必须是我方场上的卡方可更变表示形式
        var fieldID = "p1-field" + (SelectedCard.cardNo).toString();
        var cardsrc = fieldArrayPly1.FieldCards[SelectedCard.cardNo].imgsrc;
        var cardstate = fieldArrayPly1.FieldCards[SelectedCard.cardNo].state;

        switch (cardtype) {
            case 'monster':
                if (cardstate == 'attk') {
                    cardstate = "defen";
                } else if (cardstate == 'defen') {
                    cardstate = "back";
                } else if (cardstate == 'back') {
                    cardstate = "attk";
                }
                break;
            case 'magic':
                if (cardstate == 'off') {
                    cardstate = "on";
                } else {
                    cardstate = "off";
                }
                break;
            default:
                break;
        }

        fieldArrayPly1.FieldCards[SelectedCard.cardNo].state = cardstate;  //更新场上卡片状态信息
        cardstate = "change-" + cardstate;  //为通过更变形式而导致的战场更新操作添加一个标签方便更新函数识别(因为更变形式不触发音效)
        updateField(fieldID, cardstate, cardsrc);  //更新指定卡槽

        /**
         * 告知对手某一卡槽的表示形式发生变化,执行战场更新函数
         */
        var updateID = "p2-field" + (SelectedCard.cardNo).toString();
        messageField(cardstate, updateID, cardsrc);
    }
}

更变后的形式(state)会被战场数组记录并同时传递给战场更新函数 updateField 以更新所选卡槽的卡片样式。


3. 战场更新函数:

战场更新函数 updateField 用于更改某一个卡槽的卡片样式,让玩家可以实际的看到操作带来的变化,前面介绍的两个函数均有在作用域的末尾调用此函数。

参数含义
fieldID需要更新的卡槽 id
cardstate需要更新的卡片状态
cardsrc需要更新的卡牌图片 url

updateField 的原理很简单,我们在css中准备了几种卡片状态对应的卡槽样式,根据参数的不同修改卡槽样式即可(其实有些样式是一样的完全可以共用…)。

.main-field .battle-field .card-field .item .card-attk {
  width: 65px;
  height: 94px;
  margin: 1px 40px;
}

.main-field .battle-field .card-field .item .card-defen {
  width: 65px;
  height: 94px;
  transform: rotate(90deg);
  margin: 1px 40px;
}

.main-field .battle-field .card-field .item .card-back {
  width: 65px;
  height: 94px;
  transform: rotate(90deg);
  margin: 1px 40px;
}

.main-field .battle-field .card-field .item .card-on {  /* 魔法陷阱翻开状态*/
  width: 65px;
  height: 94px;
  margin: 1px 40px;
}

.main-field .battle-field .card-field .item .card-off {  /* 魔法陷阱覆盖状态*/
  width: 65px;
  height: 94px;
  margin: 1px 40px;
}
/**
 * 战场状态更新,单独更新某一个卡槽
 * @param {string} fieldID - field img container id 
 * @param {string} cardstate - state of card (attk/defen/back/on/off)
 * @param {string} cardsrc - card source url
 */
function updateField(fieldID, cardstate, cardsrc) { 
    var stateclass;
    element = document.getElementById(fieldID);

    /**
     * 如果是盖卡或背盖召唤直接显示卡片背面
     * 检查showCardInfo函数可知对于我方来说,即使卡片是背面图片仍可以显示卡片信息
     * 由于音效种类问题修改分类了多种情况
     */
    switch (cardstate) {
        case 'off':
        case 'back':
            element.src = CardBackSrc;
            stateclass = "card-" + cardstate;
            /*触发背盖或盖卡音效 */
            var snd = new Audio("sound/activate.wav");
            snd.play();
            break;
        case 'on':  //正常发动卡片
            element.src = cardsrc;
            stateclass = "card-" + cardstate;
            /*触发发动卡片音效 */
            var snd = new Audio("sound/activate.wav");
            snd.play();
            break;
        case 'change-off':  //通过更变形式覆盖卡片
            element.src = CardBackSrc;
            stateclass = "card-" + cardstate.replace("change-", "");
            break;
        case 'change-back':  //通过更变形式背盖召唤卡片
            element.src = CardBackSrc
            stateclass = "card-" + cardstate.replace("change-", "");
            break;
        case 'change-on':  //通过更变形式实现的打开盖卡
            /*触发打开盖卡音效 */
            element.src = cardsrc;
            stateclass = "card-" + cardstate.replace("change-", "");
            var snd = new Audio("sound/open.wav");
            snd.play();
            break;
        case 'null':
            stateclass = "card";
            element.src = cardsrc;
            break;
        default:
            element.src = cardsrc;
            if (cardstate.search("change-") == -1) {  //正常召唤
                stateclass = "card-" + cardstate;
                /*触发发召唤怪兽音效 */
                var snd = new Audio("sound/summon.wav");
                snd.play();
            } else {                                  //更变形式
                stateclass = "card-" + cardstate.replace("change-", "");
            }
            break;
    }

    element.setAttribute("class", stateclass);  //更新对应img容器的class
}

:这里由于添加音效的问题多出了3种状态,change-off, change-back, change-on,因为通过更变形式打开或背盖卡片与直接从手牌发动或背盖卡片的音效是不同的,故多加了几个状态区分一下。前文 changeState 函数中也有一段代码是为了这里区分而在调用战场更新前修改了状态名称:

fieldArrayPly1.FieldCards[SelectedCard.cardNo].state = cardstate;  //更新场上卡片状态信息
cardstate = "change-" + cardstate;  //为通过更变形式而导致的战场更新操作添加一个标签方便更新函数识别(因为更变形式不触发音效)
updateField(fieldID, cardstate, cardsrc);  //更新指定卡槽

最后来测试一下:

各种召唤:

summons
发动/放置 魔法陷阱:

magics
更变形式:

change_state
下一章继续介绍其他的功能按键。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Vue 中使用 xterm.jsWebSocket 实现终端,你需要将用户输入的命令发送给后端,然后将后端返回的结果输出到 xterm.js 终端中。以下是一个简单的示例: ```html <template> <div id="terminal"></div> </template> <script> import { Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; export default { data() { return { socket: null, // WebSocket 实例 term: null, // Terminal 实例 }; }, mounted() { // 创建 WebSocket 实例 this.socket = new WebSocket('ws://localhost:8080'); // 创建 Terminal 实例 this.term = new Terminal(); const fitAddon = new FitAddon(); this.term.loadAddon(fitAddon); this.term.open(document.getElementById('terminal')); // 处理 WebSocket 消息 this.socket.onmessage = (event) => { this.term.write(event.data); }; // 处理输入事件 this.term.onData(data => { this.socket.send(data); }); // 调整终端大小 this.term.onResize(size => { const cols = size.cols; const rows = size.rows; this.socket.send(JSON.stringify({ type: 'resize', cols, rows })); }); // 发送 resize 消息 const cols = this.term.cols; const rows = this.term.rows; this.socket.send(JSON.stringify({ type: 'resize', cols, rows })); }, beforeDestroy() { // 关闭 WebSocket 连接 this.socket.close(); } } </script> ``` 以上代码中,我们首先在 `mounted` 钩子函数中创建了一个 WebSocket 实例和一个 Terminal 实例。然后我们为 WebSocket 实例添加了一个 `onmessage` 事件监听器,该监听器会在接收到服务器返回的消息时触发,我们在该事件处理函数中将消息输出到终端中。 接着,我们为 Terminal 实例添加了一个 `onData` 事件监听器,该监听器会在用户输入时触发,我们在该事件处理函数中向服务器发送用户输入的命令。同时,我们还为 Terminal 实例添加了一个 `onResize` 事件监听器,该监听器会在终端大小调整时触发,我们在该事件处理函数中向服务器发送终端大小变化的消息。 最后,我们在 `beforeDestroy` 钩子函数中关闭了 WebSocket 连接。 需要注意的是,以上代码中的 WebSocket 连接是通过 `ws://localhost:8080` 连接本地服务器的,你需要根据实际情况修改 WebSocket 连接地址。另外,代码中的消息格式和处理逻辑也需要根据实际情况进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值