开发个「英雄杀记牌器」
背景
2020年,那时候很喜欢玩英雄杀,当时游戏主要推广2V2模式,还需要ban pick武将,竞技性还是很强的。
普通人玩游戏不会记牌,但是如果你想赢,一定要记牌,普通的人会记某某装备牌出现过没有、杀闪药还有几张,牛逼的人会记剩下的牌的牌类型、花色、数字,例如他记得剩下的杀是黑桃A、黑桃J、红桃K,药是……。太牛逼了。
当时我要参加一个全国巅峰赛,所以就手写了一个记牌器,帮助我记牌堆中还剩什么牌:tool.hullqin.cn/yxs。
最终,也和队友取得了全国第八的成绩,还算满意了,最后一把BP运气太差,我们选的武将必被对方克制,因为不管选啥,武将堆中都有非常克制我们的武将,太难了。
功能介绍
英雄杀的牌,分为3种:「摸牌堆」、「场上的牌」、「弃牌堆」。
- 起初,所有牌都在「摸牌堆」,各种牌的初始数量设置为各个牌的总数。
- 允许将一些牌放置到「场上的牌」区域,例如你和队友的手牌、装备区的牌、判定区的牌。
- 允许将一些牌放置到「弃牌堆」,出的牌、判定牌、判定区的牌解除后,都会扔进弃牌堆。
- 允许用户随意设置牌的位置,例如移动「场上的牌」至「弃牌堆」、移动「弃牌堆」至「摸牌堆」等,以适应战场上的多变性。
- 允许洗牌。洗牌时,所有「弃牌堆」的牌进入「摸牌堆」。「场上的牌」在洗牌时,不会被清洗,依然留在场上。
- 允许清空。相当于重开一局游戏,把所有「弃牌堆」、「场上的牌」都移动至「摸牌堆」。
- 「场上的牌」可以随意拖动位置,是为了方便观察(如果把场上的牌跟手牌的顺序对应起来,就很方便了)。
整体界面如下:
开发
HTML和CSS
不再罗列了,可以直接检查源代码:tool.hullqin.cn/yxs。
全局变量定义
const total = {
sha: 15, shan: 7, jiu: 2, yao: 3,
fhly: 2, wjqf: 1, jd: 2, jdsr: 1,
fdcx: 3, tnqw: 2, wgfd: 1, wzsy: 1, wxkj: 2, hdwl: 1, spl: 1, blcd: 1,
hf: 1, xhf: 1, lld: 1, lyq: 1, blc: 1, jgm: 1, fym: 1, yry: 1, qkd: 1
};
const using = {
sha: 0, shan: 0, jiu: 0, yao: 0,
fhly: 0, wjqf: 0, wgfd: 0, fdcx: 0, tnqw: 0, wzsy: 0, jdsr: 0, jd: 0, wxkj: 0, hdwl: 0, spl: 0, blcd: 0,
hf: 0, xhf: 0, lld: 0, lyq: 0, blc: 0, jgm: 0, fym: 0, yry: 0, qkd: 0
};
const used = {
sha: 0, shan: 0, jiu: 0, yao: 0,
fhly: 0, wjqf: 0, wgfd: 0, fdcx: 0, tnqw: 0, wzsy: 0, jdsr: 0, jd: 0, wxkj: 0, hdwl: 0, spl: 0, blcd: 0,
hf: 0, xhf: 0, lld: 0, lyq: 0, blc: 0, jgm: 0, fym: 0, yry: 0, qkd: 0
};
const used_cards = [];
const using_cards = [];
const PREFIX = 'img/';
其中total
标识所有牌的总数,using
表示「场上的牌」的数量,used
表示「弃牌堆」的数量,used_cards
表示「弃牌堆」的所有牌,按序排列,using_cards
表示「场上的牌」所有牌,按序排列。
另外PREFIX
是所有静态资源的前缀(静态资源主要是图片)。
初始化
本段逻辑主要是为了实现这种效果:
- 通过图片展示牌的类型。
- 可以展示「摸牌堆」实时剩余多少张。
- 为了让「摸牌堆」剩多少张展示更生动形象,还加了绿色的百分比进度条。
- 通过
+1
和-1
按钮,可以操控:把「摸牌堆」的牌移动到「场上的牌」、或把「摸牌堆」的牌移动到「弃牌堆」。
function html_init() {
const main = document.getElementById('main');
const used = document.getElementById('used');
for (key in total) {
const key2 = key;
const dcard = document.createElement('div');
dcard.className = 'dcard';
const dimg = document.createElement('div');
dimg.className = 'dimg';
const img = document.createElement('img');
img.alt = key;
img.style.height = '100%';
img.src = PREFIX + key + '.jpg';
img.onclick = function(){update(key2, 'using', 1);};
img.oncontextmenu = function (e) {
e.preventDefault();
update(key2, 'used', 1);
};
dimg.appendChild(img);
dimg.style.textAlign = 'center';
const dleft = document.createElement('div');
dleft.className = 'dleft';
let text = document.createElement('span');
text.innerHTML = '牌堆剩 ';
dleft.appendChild(text);
const sleft = document.createElement('span');
const sleft2 = document.createElement('span');
sleft2.id = key + '_left';
sleft.style.display = 'flex';
sleft.style.border = '1px solid';
sleft.style.flexGrow = '1';
sleft2.style.width = '0';
sleft2.style.background = '#98da98';
sleft.appendChild(sleft2);
dleft.appendChild(sleft);
const dusing = document.createElement('div');
dusing.className = 'dusing';
text = document.createElement('span');
text.innerHTML = '场上:';
dusing.appendChild(text);
const susing = document.createElement('span');
susing.id = key + '_using';
dusing.appendChild(susing);
const susing2 = document.createElement('span');
susing2.className = 'sbtn';
const btn1 = document.createElement('button');
btn1.className = 'b1';
btn1.onclick = function(){update(key2, 'using', 1);};
btn1.innerHTML = '+1';
susing2.appendChild(btn1);
const btn2 = document.createElement('button');
btn2.className = 'b-1';
btn2.onclick = function(){update(key2, 'using', -1);};
btn2.innerHTML = '-1';
susing2.appendChild(btn2);
dusing.appendChild(susing2);
const dused = document.createElement('div');
dused.className = 'dused';
text = document.createElement('span');
text.innerHTML = '弃牌:';
dused.appendChild(text);
const sused = document.createElement('span');
sused.id = key + '_used';
dused.appendChild(sused);
const sused2 = document.createElement('span');
sused2.className = 'sbtn';
const btn3 = document.createElement('button');
btn3.className = 'b1';
btn3.onclick = function(){update(key2, 'used', 1);};
btn3.innerHTML = '+1';
sused2.appendChild(btn3);
const btn4 = document.createElement('button');
btn4.className = 'b-1';
btn4.onclick = function(){update(key2, 'used', -1);};
btn4.innerHTML = '-1';
sused2.appendChild(btn4);
dused.appendChild(sused2);
dcard.appendChild(dimg);
dcard.appendChild(dleft);
dcard.appendChild(dusing);
dcard.appendChild(dused);
dcard.id = 'main_' + key;
main.appendChild(dcard);
}
}
function update_all() {
for (let key in total) {
update_one(key);
}
update_total_left();
}
添加一个「场上的牌」
function add_one_using(card) {
using_cards.push(card);
const eusing = document.getElementById('using');
const div = document.createElement('div');
const img = document.createElement('img');
const guid = new Date().valueOf().toString();
img.src = PREFIX + card + '.jpg';
img.style.height = '100%';
img.alt = card;
img.draggable = false;
img.onclick = function() {
if (! update(card, 'using', -1, guid)) update(card, 'used', 1);
};
img.oncontextmenu = function(e) {
e.preventDefault();
update(card, 'using', -1, guid);
};
div.className = 'using-dimg';
div.draggable = true;
div.setAttribute('name', 'using_' + card);
div.id = guid;
div.appendChild(img);
div.ondragstart = function(e) {e.dataTransfer.setData("Text", e.target.id);};
div.ondragover = function(e) {e.preventDefault();};
div.ondrop = function(e) {
e.preventDefault();
const data = e.dataTransfer.getData("Text");
if (data.length < 6) return;
const src = document.getElementById(data);
if (src.getAttribute('name').substr(0, 4) === 'used') return;
const dst = e.target.parentNode;
if (src.offsetLeft < dst.offsetLeft) {
dst.parentNode.insertBefore(src, dst);
} else if (src.offsetLeft > dst.offsetLeft) {
dst.parentNode.insertBefore(src, dst.nextElementSibling);
}
};
eusing.insertBefore(div, eusing.firstChild.nextSibling.nextSibling);
}
删除一个「场上的牌」
function del_one_using(index, guid=null) {
if (guid === null) {
document.getElementsByName('using_' + using_cards[index])[0].remove();
} else {
document.getElementById(guid).remove();
}
using_cards.splice(index, 1);
}
添加一个「弃牌堆」的牌
function add_one_used(card) {
used_cards.push(card);
const eusing = document.getElementById('used');
const div = document.createElement('div');
const a = document.createElement('a');
const img = document.createElement('img');
const guid = new Date().valueOf().toString();
img.src = PREFIX + card + '.jpg';
img.style.height = '100%';
img.alt = card;
img.ondragstart = function(e) {e.dataTransfer.setData("Text", e.target.parentNode.parentNode.id);};
a.onclick = function(){
if (! update(card, 'used', -1, guid)) update(card, 'using', 1);
};
a.appendChild(img);
div.className = 'used-dimg';
div.setAttribute('name', 'used_' + card);
div.id = guid;
div.appendChild(a);
eusing.appendChild(div);
}
删除一个「弃牌堆」的牌
function del_one_used(index, guid=null) {
if (guid === null) {
document.getElementsByName('used_' + used_cards[index])[0].remove();
} else {
document.getElementById(guid).remove();
}
used_cards.splice(index, 1);
}
备注
这是以前写的代码了,比较粗糙,但是至少实现了「英雄杀记牌器」的功能。感兴趣的可以去网站看一下,都是原生JS,没有混淆。
写在最后
我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,联系我,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这2个专栏里分享:《教你做小游戏》、《极致用户体验》。
转载自:https://juejin.cn/post/7135843844922441741