HTML5 + Canvas + 广度优先搜索(BFS) 编写lol连连看

HTML5 + Canvas + 广度优先搜索(BFS) 编写lol连连看

HTML5 + Canvas + 广度优先搜索(BFS) 编写lol连连看


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

使用 BFS 广度优先搜索编写 连连看。

image-20210420111955474

起点是b,终点是e

检查b的上下左右(当前位置加 x:0,1,0,-1|y:-1,0,1,0) ,如果不为h(没有连对的英雄障碍物)则放到队列中,循环读取队列直到找到终点e


# 效果

my_animation

一、准备数据

1.使用python 爬取lol官网获取头像图片

# -*- coding: utf-8 -*

import requests
import json
import os
from tqdm import tqdm

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36'
}
url = 'https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js'

html_data = requests.get(url, headers=headers)
img_out_path = os.path.join(os.path.join(os.getcwd(), ".."), "hero_img")
if not os.path.exists(img_out_path):os.makedirs(img_out_path)
heros = json.loads(html_data.text)
for h in tqdm(heros["hero"]):
    img_url = "https://game.gtimg.cn/images/lol/act/img/champion/" + h["alias"] + '.png'
    img = requests.get(img_url,headers=headers)
    if img.status_code == 200:
        img_name = os.path.join(img_out_path, h["alias"]+".png")
        with open(img_name,'wb') as f:
            f.write(img.content)

image-20210419165915887

得到头像图片

二、html代码

创建两个画布,main-canvas 用来显示图片,main-canvas2 用来显示选中状态还有连线

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Simple Canvas Game</title>
		<!--style="position:relative; z-index:-1"-->
		<div>
			<div style="position:relative; z-index:-1"  >
				<canvas id="main-canvas"></canvas>
			</div>
			<canvas style="position:absolute;top:0;left:0; z-index:9999" id="main-canvas2"></canvas>
		</div>
		

		<div id="container"></div>
	</head>
	<body>
		<script src="js/heros.js"></script>
		<script src="js/game.js"></script>	
	</body>
</html>

三、js

hero.js

#字符串的拼接 可以用python print(os.listdir("../hero_img"))

var heros = ['Aatrox.png', 'Ahri.png', 'Akali.png', 'Alistar.png', 'Amumu.png', 'Anivia.png', 'Annie.png', 'Aphelios.png', 'Ashe.png', 'AurelionSol.png', 'Azir.png', 'Bard.png', 'Blitzcrank.png', 'Brand.png', 'Braum.png', 'Caitlyn.png', 'Camille.png', 'Cassiopeia.png', 'Chogath.png', 'Corki.png', 'Darius.png', 'Diana.png', 'Draven.png', 'DrMundo.png', 'Ekko.png', 'Elise.png', 'Evelynn.png', 'Ezreal.png', 'FiddleSticks.png', 'Fiora.png', 'Fizz.png', 'Galio.png', 'Gangplank.png', 'Garen.png', 'Gnar.png', 'Gragas.png', 'Graves.png', 'Hecarim.png', 'Heimerdinger.png', 'Illaoi.png', 'Irelia.png', 'Ivern.png', 'Janna.png', 'JarvanIV.png', 'Jax.png', 'Jayce.png', 'Jhin.png', 'Jinx.png', 'Kaisa.png', 'Kalista.png', 'Karma.png', 'Karthus.png', 'Kassadin.png', 'Katarina.png', 'Kayle.png', 'Kayn.png', 'Kennen.png', 'Khazix.png', 'Kindred.png', 'Kled.png', 'KogMaw.png', 'Leblanc.png', 'LeeSin.png', 'Leona.png', 'Lillia.png', 'Lissandra.png', 'Lucian.png', 'Lulu.png', 'Lux.png', 'Malphite.png', 'Malzahar.png', 'Maokai.png', 'MasterYi.png', 'MissFortune.png', 'MonkeyKing.png', 'Mordekaiser.png', 'Morgana.png', 'Nami.png', 'Nasus.png', 'Nautilus.png', 'Neeko.png', 'Nidalee.png', 'Nocturne.png', 'Nunu.png', 'Olaf.png', 'Orianna.png', 'Ornn.png', 'Pantheon.png', 'Poppy.png', 'Pyke.png', 'Qiyana.png', 'Quinn.png', 'Rakan.png', 'Rammus.png', 'RekSai.png', 'Rell.png', 'Renekton.png', 'Rengar.png', 'Riven.png', 'Rumble.png', 'Ryze.png', 'Samira.png', 'Sejuani.png', 'Senna.png', 'Seraphine.png', 'Sett.png', 'Shaco.png', 'Shen.png', 'Shyvana.png', 'Singed.png', 'Sion.png', 'Sivir.png', 'Skarner.png', 'Sona.png', 'Soraka.png', 'Swain.png', 'Sylas.png', 'Syndra.png', 'TahmKench.png', 'Taliyah.png', 'Talon.png', 'Taric.png', 'Teemo.png', 'Thresh.png', 'Tristana.png', 'Trundle.png', 'Tryndamere.png', 'TwistedFate.png', 'Twitch.png', 'Udyr.png', 'Urgot.png', 'Varus.png', 'Vayne.png', 'Veigar.png', 'Velkoz.png', 'Vi.png', 'Viego.png', 'Viktor.png', 'Vladimir.png', 'Volibear.png', 'Warwick.png', 'Xayah.png', 'Xerath.png', 'XinZhao.png', 'Yasuo.png', 'Yone.png', 'Yorick.png', 'Yuumi.png', 'Zac.png', 'Zed.png', 'Ziggs.png', 'Zilean.png', 'Zoe.png', 'Zyra.png']

game.js

1 hero

​ 英雄类,控制是否显示,是否选中,当前坐标位置和二维数组的index ,copy方法是复制当前对象

2.Queue

​ 队列,先进先出

3.point

​ 点位对象,入参 英雄对象,获取英雄对象的 坐标位置 和数组下标

4.parent

​ 画地图时存的对象,存的上一个点位的坐标和下标

5.game

​ 5.1 init

​ 初始化画布,初始化英雄列表,初始化点击方法

​ 5.2 plant_rect

​ 根据 xy 宽高 画遮罩层

​ 5.3 erase

​ 清除画线画布 的线和 遮罩

​ 5.4 hero_click

​ 点击方法,根据坐标获取选中的英雄,画遮罩层,判断当前是否有两个选中 ,如果是 是否相同,相同的话调取画线方法,画线方法返回布尔值 代表画线是否 成功,成功后清除画布

​ 5.5 plant_line

​ 画线方法,判断两个英雄中间是否有 英雄,使用广度优先搜索

​ 5.6 getRandomHeros

​ 随机获取heros对象里的英雄 并复制一份

​ 5.7 random_sort

​ 随机英雄列表

/**英雄*/
class hero{
    constructor(src,name,xy,pos,show=true,selected=false){
        this.image = new Image()
        this.image.src = src
        this.name = name;    //名称
        this.show  = show; //默认显示
        // this.selected = selected; //默认没有选中
        this.xy = xy;  //元素位置
        this.pos = pos; //数组坐标
    }

    copy(){
        let src = this.image.src;
        let name = this.name;
        let xy = this.xy;
        let pos = this.pos;
        let h2 = new hero(src,name,xy,pos)
        return h2;
    }

    toString(){
        return JSON.stringify(this)
    }
}
/**队列*/
class Queue{
    constructor(){
        this.items=[];
    }
    //入队
    enqueue() {
        var len = arguments.length;
        if (len == 0) {
            return;
        }
        for (var i = 0; i < len; i++) {
            this.items.push(arguments[i])
        }
    }
    //出队
    dequeue() {
        var result = this.items.shift();
        return typeof result != 'undefined' ? result : false;
    }

    //出队
    rdequeue() {
        var result = this.items.pop();
        return typeof result != 'undefined' ? result : false;
    }

    //队列是否为空
    isEmpty() {
        return this.items.length == 0;
    }
    //返回队列长度
    size() {
        return this.items.length;
    }
    //清空队列
    clear() {
        this.items = [];
    }
    //返回队列
    show() {
        return this.items;
    }
}

class point{
    constructor(hero) {
        let xy = hero.xy.split(",");
        let pos = hero.pos.split(",");
        this.x = parseInt(xy[0]);
        this.y = parseInt(xy[1]);
        this.pos_y = parseInt(pos[0]);
        this.pos_x = parseInt(pos[1]);

    }
}

class parent{
    constructor() {
        this.px = 0;
        this.py = 0;
        this.step = 0;
    }
}
var game = {
    canvas: document.getElementById("main-canvas"),
    plant_canvas: document.getElementById("main-canvas2"), //画线
    ctx: null,
    plant_ctx: null,
    then: null,
    requestAnimationFrame: null,
    lol_arr: new Array(),
    lol_arr_map: new Array(),
    score: 0,
    selected_hero: {},
    /**初始化*/
    init: function(){
        this.ctx = game.canvas.getContext('2d');
        this.plant_ctx = game.plant_canvas.getContext('2d');
        w = window;
        this.requestAnimationFrame = w.requestAnimationFrame || w.webkitRequestAnimationFrame || w.msRequestAnimationFrame || w.mozRequestAnimationFrame;
        this.canvas.width = 1920,this.canvas.height = 1080;
        this.plant_canvas.width = 1920,this.plant_canvas.height = 1080;
        this.lol_arr = this.getRandomHeros()
        this.lol_arr_map = JSON.parse(JSON.stringify(this.lol_arr));
        this.plant_canvas.addEventListener('click', this.hero_click, false);
    },
    /**选中遮罩*/
    plant_rect:function (x,y,w,h){
        this.plant_ctx.fillStyle = "rgba(192, 80, 77, 0.7)";
        this.plant_ctx.fillRect(x,y,w,h);
    },
    /**清除画线和遮罩*/
	erase:function(){
        let _this = game;
        setTimeout(function(line){
            //获取当前时间
            _this.plant_ctx.clearRect(0,0,1920,1080);
            _this.ctx.clearRect(0,0,1920,1080);
        },1000);
    },
    /**点击*/
    hero_click: function(e){
        let _this = game;
        let x = Math.floor(e.clientX/120);
        let y = Math.floor(e.clientY/120);
        let h = _this.lol_arr[y][x];
        if(h == undefined || h.constructor != hero){
            _this.selected_hero = {};
            _this.erase();
            return;
        }
        let xy = h.xy.split(",");
        _this.plant_rect(xy[0],xy[1],120,120);

        if(JSON.stringify(_this.selected_hero) == "{}"){
            _this.selected_hero = h;
        }else{
            if(_this.selected_hero.toString() != h.toString() && _this.selected_hero.name == h.name){
                console.log("一致")
                console.log(new point(_this.selected_hero),new point(h))
                let flag = _this.plant_line(new point(_this.selected_hero),new point(h));
                if(flag){
                    _this.selected_hero.show = false;
                    h.show = false;
                }
            }else{
                console.log("不一致")
            }
            _this.selected_hero = {};
            _this.erase();
        }

        console.log("选中了",h);
    },
    /**4方位*/
    plant_line: function(begin,end){
        let _this = game;
        let q = new Queue();
        q.enqueue(begin)
        let dx = [0,1,0,-1];  //x方向
        let dy = [-1,0,1,0];  //y方向

        let map = new Array();
        for(var i = 0; i<8; i++) {
            for (var i2 = 0; i2 < 11; i2++) {
                if(map[i] == undefined){
                    map[i] = new Array();
                }
                map[i][i2] = new parent();
            }
        }
        /**BFS*/
        while(!q.isEmpty()){
            let p = q.dequeue();

            if(p.pos_x == end.pos_x && p.pos_y == end.pos_y) {
                let x = p.pos_x;
                let y = p.pos_y;
                let tmpx = 0;
                let tmpy = 0;
                _this.plant_ctx.beginPath();
                _this.plant_ctx.lineWidth = 15;
                _this.plant_ctx.strokeStyle = "#005500";
                _this.plant_ctx.moveTo(end.x+60,end.y+60);
                //打印
                while(x != begin.pos_x || y != begin.pos_y) {
                    tmpx = map[y][x].px;
                    tmpy = map[y][x].py;
                    x = tmpx;
                    y = tmpy;
                    _this.plant_ctx.lineTo(x*120+60,y*120+60);
                    _this.plant_ctx.stroke();
                }
                _this.plant_ctx.closePath();
                _this.lol_arr_map[begin.pos_y][begin.pos_x] = begin.toString();
                _this.lol_arr_map[end.pos_y][end.pos_x] = begin.toString();
                return true;
            }else{
                for(var i = 0;i < 4; i++){
                    let nx = p.pos_x + dx[i];
                    let ny = p.pos_y + dy[i];
                    if(nx >= 0 && nx < 11 && ny >= 0 && ny < 8) {
                        if(_this.lol_arr_map[ny][nx].constructor == String){
                            if(map[ny][nx].step == 0){
                                let p2 = new point(new hero("","",[ny,nx].toString(),[ny,nx].toString()));
                                q.enqueue(p2);
                                map[ny][nx].step = map[p.pos_y][p.pos_x].step + 10;
                                map[ny][nx].px = p.pos_x;
                                map[ny][nx].py = p.pos_y;
                            }
                        }else if(nx == end.pos_x && ny == end.pos_y){
                            q.clear();
                            let p3 = new point(new hero("","",[ny,nx].toString(),[ny,nx].toString()));
                            q.enqueue(p3);
                            map[ny][nx].step = map[p.pos_y][p.pos_x].step + 10;
                            map[ny][nx].px = p.pos_x;
                            map[ny][nx].py = p.pos_y;
                            break;
                        }

                    }
                }
            }
        }
        return false;
    },
    /**生成随机数组*/
    getRandomHeros: function(){
        var arr = new Array();
        // var i = 0,i2 = 0;
        let last_hero = new hero();
        let num = 1;
        for(var i = 0; i<8; i++){
            for(var i2 = 0; i2<11; i2++){
                if(arr[i] == undefined){
                    arr[i] = new Array()
                }
                //四周加上默认值
                if(i==0 || i == 7 || i2 == 0 || i2 ==10){
                    arr[i][i2] = [i,i2].toString()
                }else{
                    let h;
                    if(num%2 == 0){
                        last_hero.xy = [i2*120,i*120].toString();
                        last_hero.pos = [i,i2].toString();
                        h = last_hero.copy();
                    }else{
                        let xy= [i2*120,i*120];
                        let r = Math.ceil(Math.random() * heros.length-1) //从英雄池里随机获取
                        let h_n = heros[r]; //英雄名
                        h = new hero(src="../hero_img/"+h_n,name=h_n.split(".")[0],xy=xy.toString(),pos=[i,i2].toString()) //英雄
                        last_hero = h.copy();
                    }
                    arr[i][i2] = h
                }
                num++;
            }
        }
        arr = game.random_sort(arr)
        //打乱顺序
        return arr;
    },
    /**数组随机*/
    random_sort: function(dic){
        let ylen = dic.length;
        let xlen = dic[0].length;
        for(var i = 0; i<ylen; i++) {
            for (var i2 = 0; i2 < xlen; i2++) {
                let yr = Math.ceil(Math.random() * (ylen-2))
                let xr = Math.ceil(Math.random() * (xlen-2));
                let h = dic[yr][xr];
                if (h.constructor === hero && dic[i][i2].constructor === hero){
                    let lsh = dic[yr][xr].copy();
                    let lsh2 = dic[i][i2].copy();
                    dic[yr][xr].name = lsh2.name;
                    dic[i][i2].name = lsh.name;
                    dic[yr][xr].image = lsh2.image;
                    dic[i][i2].image = lsh.image;
                }
            }
        }
        return dic;
    },
    render: function(){
        for(var i = 0; i<8; i++){
            for(var i2 = 0; i2<11; i2++){
                let h = game.lol_arr[i][i2];
                if (h.constructor === hero){
                    if(h.show){
                        let xy_arr = h.xy.split(",")
                        game.ctx.drawImage(h.image,xy_arr[0],xy_arr[1]);
                    }
                }
            }
        }
    },
    main: function(){
        game.render();
        requestAnimationFrame(game.main)
    },

}
game.init()
game.main()

# 总结
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

matianlongg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值