[Poker] Flop Game-Detailed Explanation of WeChat Mini Program Development Process

Do you still remember playing poker games when you were a kid? There are many ways to play like this. In addition to stacking cards, you can also play flip cards. Here, the flip card game is easily implemented. It is suitable for novices to play and it is very good. Training memory, if you are interested, take a look at the implementation process.

A suggestion for newbies to get started with WeChat mini programs. Master the basic knowledge of HTML web design. It is best to familiarize yourself with some basic JavaScript knowledge and understand how to use Vue. It will be easier to learn in the future.

Open the WeChat developer tools, select the mini program, and create a new project.

For example, the project name can be filled inmimiparogram-hlf-pass, as shown below

you0

  • AppID uses its own test number
  • Not using cloud services
  • JavaScript - base template

The created project will automatically generate some files,

If you want to make a small game project, some of the files automatically created here are different from the small program project.

What is the difference? Please refer to this article WeChat Developer Tools - Importing a mini program project will automatically switch to the mini game to open the error solution

Next, open the folder pages in the mini program project.

Create a new page folder inside, a page with the namesgame

Interface layout

The mini program has a layout file, which can be used to create the game interface layout.

Mini game projects do not have layout files, and the game interface must be drawn with canvas.
Mini games are more complicated than mini programs. For example, there is no available UI framework to write , it will take more time to make,

Page layout files, file suffixes are all wxml,

Open a layout filepages/game/game.wxml, and the content of the written page is roughly as follows

<view class="column">
    <view class="column-item">
        <view class="row">
            <!-- 这头部区域,显示游戏时间和计分 -->
        </view>
        <canvas class="canvas" id="zs1028_csdn" type="2d" bindtouchstart="onTouchStart"></canvas>
        <progress percent="{
     
     {progressPercent}}"></progress>
    </view>
    <view class="column-item column-item-full">
        <!-- 这尾部区域,显示游戏说明 -->
    </view>
</view>

Some style class names for display effects, such ascolumn,column-item..., etc., are set in the style filegame.wxss,
drawing component canvas, called canvas. When the canvas is clicked, the method onTouchStart will be called. The implementation method will be discussed later

In the page layout, a background image of 5x4 playing cards is placed. The display effect is as shown below.
figure 1
It’s not over yet. When you compile and run, you will find that the page cannot be displayed normally. of,

That is the layout display part. The data variables used have not been initialized yet.

You need to handle the initialization and it will be displayed normally.

Game logic

The suffix js of some files in the project is the processing logic code file.

Open a filepages/game/game.js and start writing the game initialization logic here,

initialization

The content of the file is roughly as follows. Let’s see what the initialization logic is.

//...
Page({
    
    
    /**
     * 页面的初始数据
     */
    data: {
    
    
        timerNum:0,// 显示倒计时
        scopeCount:0,// 显示记录(分)
        errorCount:0,// 显示错误数
        progressPercent:100 // 显示进度(倒计时)
    },
    /**
     * 生命周期函数--监听页面初次渲染完成
     */
    async onReady() {
    
    
        //...
    },
    //...
})

As you can see from the above code, the page will callonReady()this method when loading,

Next, run it to see if the display is normal.

You will find that only the display of the canvas component canvas is blank.

Then write, in the onReady() method, to execute the initialization logic code,

Add code as follows

// 学过前端 JQuery 的应该熟悉吧,类似的查询工具方法
const {
    
     width, height, node:canvas } = await ZS1028_CSDN.queryAsync('#zs1028_csdn')
// 微信小程序的画布canvas的宽高需要调整一致
Object.assign(canvas, {
    
     width, height })
// 加载扑克牌图片资源对象方法,传入canaas用于创建Image对象,返回的是images集合数据
const data = await ZS1028_CSDN.loadStaticToImagesAsync(canvas)
// 这里定义一些参数值
let rows = GridRows //网格行数4,这里用全局常量表示
let cols = 5 //网格列数
//以下是计算出的单元格宽,高
let w = Math.trunc(canvas.width/cols)
let h = Math.trunc(data[0].image.height*w/data[0].image.width)
//以下是计算出画布的内边距,左边和上边
let left = canvas.width%w/2
let top = canvas.height%h/2
//获取画布的Context,绘图的方法集合
const ctx = canvas.getContext('2d')
//以下是定义和初始化网格list数据
let list = []
for(let r=0,i=0; r<rows; r++){
    
    
    for(let c=0; c<cols; c++,i++){
    
    
        //加入每个单元格的坐标x,y,以平面的横纵坐标轴计算单位
        list.push({
    
    
            x: left+c*w,
            y: top+r*h
        })
    }
}
//定义一个画布数据,将所有参数值缓存到里面
this.canvasData = {
    
    
    canvas,
    context:ctx,
    grid:{
    
     
        width:w, 
        height:h 
    },
    size:cols,
    paddingLeft:left,
    paddingTop:top,
    data,
    list,
    showIndex:-1
}
//执行开始动画方法,发牌动画
await this.restartAsync()
//展示对话框,开始游戏确认
await ZS1028_CSDN.showModalAsync('扑克翻牌游戏已准备好,请点击开始游戏,加油(ง •_•)ง','开始游戏')
//开始计时
this.startTimer()

Look at the above code, is it easy to understand?
Why are some methods followed by Async()? This is used to distinguish asynchronous methods, < /span>

asynchronous method

what is async method,

  • The code in the method will not be executed immediately;
  • Do not wait for the result to be returned before executing the next step;
  • No return value, if the parameter is passed in, there will be a callback functionFunction;
  • If there is a return value, it must be the returnedPromise object;

How to call asynchronous methods is a bit difficult for beginners to understand.
If you don’t understand, you can put it down for the time being and think about it later when you improve your level;

Here, the asynchronous method is changed to synchronous execution, which is easy for beginners to understand.

  • To execute the asynchronous method until the end, you need to add await in front,
  • Before addingawait, check whether the name of the method that calls it is preceded by async;

Look at the above code, the ZS1028_CSDN module is used. This module is written by the author of TA Yuanfang himself , which encapsulates some methods that need to be called. Now you can understand it just by looking at the method name and comments.

How to implement it? There is not much code encapsulated in it, less than 100 lines. The code inside seems complicated.

If you don't understand, you should recognize that your level is not good enough. It is recommended to study slowly and make money when you learn.

If you want to learn, just look at the project source code. If you can study it and understand it, maybe you will reach the same level as a senior programmer.

Sorry, I digressed. Let’s continue.

Games start

Call the method at the beginning of the gamerestartAsync() to implement the card dealing animation,

You can see that async was mentioned earlier. This is an asynchronous method. The code is as follows. Let’s see what the processing logic is.

async restartAsync(){
    
    
    // 将需要的一些参数值取出来用
    const {
    
     data, list, canvas, context:ctx, size:cols, grid } = this.canvasData
    const {
    
     width:w, height:h } = grid
    // 网格行数
    let rows = GridRows
    // indexs 是随机存放的牌索引组合,rows/2*cols=10
    let indexs = ZS1028_CSDN.getRandomListIndexs(data.slice(1).map((m,i)=>i+1),rows/2*cols)
    // 将索引顺序再次打乱,得indexs2
    let indexs2 = ZS1028_CSDN.getRandomListIndexs(indexs)
    // 两个加起来,就是20张扑克牌的数量了
    indexs = indexs.concat(indexs2)
    // 遍历一遍,把每个牌的索引设置到网格中
    list.forEach((grid,index)=>{
    
    
        grid.index = indexs[index] //设置索引
        grid.count = 0 //重置牌被翻过的次数
    })
    // 如果没有,给其赋上默认值,90秒
    if (this.maxTimerNum==undefined) this.maxTimerNum = 90

    this.setData({
    
    
        timerNum:MinTimerNum+this.maxTimerNum //倒计时计数: 30 + 90 = 120 秒
    })
    // 给画布绘制上背景色
    ctx.fillStyle='white'
    ctx.rect(0,0,canvas.width,canvas.height)
    ctx.fill()
    // 在调用异步方法前,设置等待状态,防止用户触摸点击处理
    this.isWait = true
    // 调用一个异步的遍历列表方法,执行每一个发牌的动画
    await ZS1028_CSDN.eachListAsync(list, (grid)=>ZS1028_CSDN.startAnimationAsync(canvas, {
    
    
        duration: 300, //动画时长,毫秒
        fromX: (canvas.width-w)/2, // 起始坐标
        fromY: canvas.height,
        toX: grid.x, // 目的坐标
        toY: grid.y,
        image: data[0].image, //第一个图片,是牌的背面图片
        width: w,
        height: h
    }))
    // 以上计算出:300 * 20张牌 = 6000毫秒, 就是所有动画完成时间大约6秒
    this.isWait = false //上面异步方法执行完,表示整个动画结束了,恢复一下这个状态
},

You will find that the moduleZS1028_CSDN encapsulates some asynchronous methods, because the execution of animation is asynchronous,
Here we just change the asynchronous methods into synchronous execution. Doesn’t it seem easy to understand

Timing logic

When the player clicks the Start Game button in the game prompt, the start timing method will be called.

The timing method isstartTimer(), the code is as follows,

startTimer() {
    
        
    this.timer = setInterval(()=>{
    
    
        let {
    
    timerNum} = this.data
        timerNum--
        if(timerNum<=0){
    
    
            this.closeTimer() //关闭定时器
            this.showModalForGame('游戏时间已用完!') //弹出对话框提示玩家游戏结束了
        }
        //更新显示
        this.setData({
    
    
            timerNum, //定时计数的
            progressPercent: Math.trunc(timerNum*100/(this.maxTimerNum+MinTimerNum)) //进度条的
        })
    },1100)
}

Next, we will deal with the rules for players to clear the level. All cards must be eliminated within the set time to clear the level, otherwise the game will end.

The triggering timing for clearing a level should be dealt with based on the player’s logic of turning over cards.

Flop judgment

When the player touches the canvas, the touch start event method bound to the canvas will be called.

In this methodonTouchStart(e), the flop operation is implemented. The code is as follows

async onTouchStart(event){
    
    
    // 如果是在进行动画未完成,是不处理点击的
    if (this.isWait) return
    // 如果有已打开的第二张牌,没有来得及关闭的就先处理关闭
    if (this.isWaitClose) this.isWaitClose.close()
    // 开始处理触摸事件
    const touch = event.touches[0]
    const {
    
     data, list, canvas, context:ctx, paddingLeft:left, paddingTop:top, grid, size, showIndex } = this.canvasData
    // 判断是否在触摸到牌区域范围内
    if (!(left<touch.x && top<touch.y && canvas.width-left>touch.x && canvas.height-top>touch.y)) return
    // 根据坐标计算在网格中的位置
    let col = Math.trunc((touch.x-left)/grid.width)
    let row = Math.trunc((touch.y-top)/grid.height)
    let index = row*size+col
    // 如果不在网格中
    if (index>=list.length) return
    let selectGrid = list[index]
    // 返回指定的单元格的扑克牌索引
    let retIndex = selectGrid.index
    if (retIndex<0) return
    // 通过索引取出选择到的扑克牌图像数据
    let selectImage = data[retIndex]
    // 判断是否与上次选择的同一个位置单元格牌
    if (showIndex==index) {
    
    
        // 如果是,直接处理将翻开的牌关闭,然后绘制牌
        this.canvasData.showIndex = -1
        this.drawImage(data[0].image, row, col)
        return
    }
    // 绘制一下,这里是将关闭的牌翻开了
    this.drawImage(selectImage.image, row, col)
    // 判断上次是否有翻开的牌,
    if (showIndex>=0) {
    
    
        // 将两个翻开的牌拿来比较
        let beforeSelectGrid = list[showIndex]
        let beforeSelectImage = data[beforeSelectGrid.index]
        // 判断牌的id是否一致,一样的牌
        if (beforeSelectImage.id == selectImage.id) {
    
    
            this.isWaitClose?.close() // 将没有关闭的牌关闭
            this.clearGrid(index) // 擦除指定绘制的牌
            this.canvasData.showIndex = -1
            this.isWait = true // 设置等待状态,开始动画
            let toX = (canvas.width-grid.width)/2 // 设置目标坐标
            let toY = canvas.height
            let duration = 600
            // 处理拿走第一个牌的动画
            await ZS1028_CSDN.startAnimationAsync(canvas, {
    
    
                duration,
                fromX: selectGrid.x,
                fromY: selectGrid.y,
                toX,
                toY,
                image: selectImage.image,
                width: grid.width,
                height: grid.height
            })
            this.clearGrid(showIndex)
            // 处理拿走第二个牌的动画
            await ZS1028_CSDN.startAnimationAsync(canvas, {
    
    
                duration,
                fromX: beforeSelectGrid.x,
                fromY: beforeSelectGrid.y,
                toX,
                toY,
                image: beforeSelectImage.image,
                width: grid.width,
                height: grid.height
            })
            this.isWait = false //动画结束,取消等待状态
            // 判断所有的牌,过滤出没翻开的牌还有多少
            let {
    
     timerNum, scopeCount:scope, errorCount } = this.data
            scope += ScopeStep
            if (list.filter(grid=>grid.index>0).length<=0){
    
    
                this.closeTimer()
                // 这里计算游戏得分,并更新展示...
            }
            // 更新分数记录
            this.setData({
    
    
                scopeCount:scope
            })
            return
        }else if (selectGrid.count>0){
    
    
            let {
    
     errorCount } = this.data
            // 如果这个牌被翻过还要翻,说明没记住,就更新错误记录
            this.setData({
    
    
                errorCount:errorCount+1
            })
        }
        selectGrid.count++
        await this.waitCloseAsync()
        // 绘制翻开的牌
        this.drawImage(data[0].image, row, col) 
        return
    }
    // 更新选择的
    this.canvasData.showIndex = index
},

When the user turns over the cards, points will be scored for correct judgments, and demerits will be recorded for wrong judgments.

  • If each card is not correct after being turned over more than two times, it will be recorded as a mistake;
  • There will be bonus points for those who complete it. The earlier the completion, the more bonus points you will get;

game over

If the countdown is over or all the cards are eliminated, the game will end.

At the end of the game, the score is calculated and the final pop-up window is displayed. The code is as follows

let msg = '' //用于显示游戏消息
// 记错次数多,会影响奖励分发放的
if (errorCount>0) {
    
    
    let count = scope + timerNum - errorCount*2
    if (count>scope) {
    
    
        scope = count
        msg = '已追加奖励记录分,'
    }
}else{
    
    
    scope += timerNum
    msg = '已追加奖励记录分,'
}
if (this.historyScopeCount < scope){
    
    
    // 刷新记录,就存到本地,下次打开展示最高记录
    app.setHistoryScopeCount(scope)
    msg = `游戏结束,恭喜刷新记录:${
      
      scope}`+msg
}else{
    
    
    msg = `游戏结束,恭喜过关,当前记录:${
      
      scope}`+msg
}
// 弹出对话框,输出游戏成绩
this.showModalForGameAsync(msg+'是否继续?',true)    
this.setData({
    
    
    scopeCount:scope
})

Run tests

That’s it for now. Do you understand all the above codes?

After writing the code, you can basically run the tests.

Then the running effect of the WeChat applet project, the recorded animation is as follows
Insert image description here

Is the card dealing animation smooth? A timer is used in the implementation, and the minimum delay can be modified.
Whether it is smooth in the end depends on the performance of the device, which is very suitable for drawing animation

This kind of implementation idea can also be implemented on WeChat mini games.

You can refer to the article[Snake] Detailed explanation of how to transfer the game from WeChat mini program to mini game,

The WeChat mini game project has been sorted out. The running effect is the same as the mini program. The recorded animation is as follows
Insert image description here

By comparison, we found that since the WeChat mini-game does not have a title bar, we drew it ourselves.

This little game can help you find out who has the worst memory ability. If you don’t admit defeat, you can practice more. Even children like to play it.

If you want the project source code, please click here to find and download, (you may not be able to view it on your mobile phone, please use your computer to browse it) View with a browser), look for the project name insideFlip Pokerkeyword source code, please feel free to download, thank you for your support!
Please add image description

Guess you like

Origin blog.csdn.net/zs1028/article/details/134083333