0%

ObjectPool.ts

/**
* 可以加入对象池的对象
*
* @export
* @interface IPoolObject
*/
export interface IPoolObject {
/**
* 重置的方法
*
* @memberof IPoolObject
*/
reset() : void
}

/**
* 对象池
*
* @export
* @class ObjectPool
*/
export class ObjectPool {
private static _instance: ObjectPool
private constructor() {}
static get Inst() : ObjectPool {
if (!ObjectPool._instance) {
ObjectPool._instance = new ObjectPool()
}
return ObjectPool._instance
}
private _poolMap: Map<any, Array<any>> = new Map<any, Array<any>>()

/**
* 释放有对象池
*
* @template T
* @param {{ new(): T}} Class
* @returns {boolean}
* @memberof ObjectPool
*/
hasPool<T extends IPoolObject>(Class: { new(): T}) : boolean {
return !!this._poolMap.get(Class)
}

/**
* 加入对象池
*
* @template T
* @param {T} obj
* @memberof ObjectPool
*/
put<T extends IPoolObject>(obj: T) : void {
if (!this._poolMap.get(obj)) {
this._poolMap.set(obj, new Array<T>())
}
// 重置对象
obj.reset()
this._poolMap.get(obj.constructor).push(obj)
}

/**
* 取出数据
*
* @template T
* @param {{ new(): T }} Class
* @returns {T}
* @memberof ObjectPool
*/
get<T extends IPoolObject>(Class:{ new(): T }) : T {
if (!this._poolMap.get(Class)) {
this._poolMap.set(Class, new Array<T>())
}
let pool = this._poolMap.get(Class)
if (!pool.length) {
this._factoryCreate(Class, pool)
}
return pool.pop()
}

/**
* 初始化对象池
*
* @template T
* @param {{ new(): T}} Class
* @param {number} [count=10]
* @memberof ObjectPool
*/
initPool<T extends IPoolObject>(Class: { new(): T}, count: number = 10) : void{
if (!this._poolMap.get(Class)) {
this._poolMap.set(Class, new Array<T>())
}
let pool = this._poolMap.get(Class)
if (!pool.length) {
this._factoryCreate(Class, pool, count)
}
}

/**
* 清除所有
*
* @memberof ObjectPool
*/
clearAll() {
// TODO 如果需要释放资源 在这里做
this._poolMap = new Map<any, Array<any>>()
}

/**
* 批量创建对象
*
* @private
* @template T
* @param {{ new(): T}} Class
* @param {number} [count=10]
* @memberof ObjectPool
*/
private _factoryCreate<T> (Class: { new(): T}, pool: Array<T>, count: number = 10) : void {
for (let i = 0; i < count; i++) {
pool.push(new Class())
}
}

}

阅读所需基础技能

  • colyseus
  • cocos creator
  • 状态同步

游戏效果

OX棋

OX棋
OX棋

服务器房间主要代码

import { Room, Delayed, Client } from "colyseus";
import { type, Schema, MapSchema, ArraySchema } from '@colyseus/schema';

// 一个回合的时间(秒)
const TURN_TIMEOUT = 10
// 棋盘宽度
const BOARD_WIDTH = 3

class Player extends Schema {
@type('string')
id: string
@type('string')
name: string
}

// 状态同步的内容
class State extends Schema {
@type('string')
currentTurn: string
@type({map: Player })
players = new MapSchema<Player>()
@type(['number'])
board: number[] = new ArraySchema<number>(0, 0, 0, 0, 0, 0, 0, 0, 0);
@type('string')
winner: string
@type('boolean')
draw: boolean
}


export class TicTacTocRoom extends Room<State> {
maxClients = 2
randomMoveTimeout: Delayed

onCreate (options: any) {
this.setState(new State())
// 消息回调
this.onMessage("*", this.messageHandler.bind(this))

}

messageHandler(client: Client, type: any, message: any) {
// 当前已经有赢家或者为平局
if (this.state.winner || this.state.draw) {
return
}
// 当前玩家的回合
if (client.sessionId === this.state.currentTurn) {
let data: any = message
const playerIds = Object.keys(this.state.players)
const index = data.x + BOARD_WIDTH * data.y
if (this.state.board[index] === 0) {
const move = (client.sessionId === playerIds[0]) ? 1 : 2
this.state.board[index] = move
if (this.checkWin(data.x, data.y, move)) {
this.state.winner = client.sessionId
} else if (this.checkBoardComplete()) {
this.state.draw = true;
} else {
// 获取另一个玩家的id
const otherPlayerSessionId = (client.sessionId === playerIds[0]) ? playerIds[1] : playerIds[0]
// 设置当前的轮回
this.state.currentTurn = otherPlayerSessionId
// 开启自动移动定时器
this.setAutoMoveTimeout()
}
}
}
}

onJoin (client: Client, options: any) {
let player = new Player()
player.id = client.sessionId
player.name = options.name || this.randomName()
this.state.players[client.sessionId] = player
if (Object.keys(this.state.players).length === 2) {
// 当前的turn
this.state.currentTurn = client.sessionId
this.setAutoMoveTimeout()
// 锁定当前的房间
this.lock()
}
}

randomName() {
return ~~(Math.random() * 10000) + ''
}

// 设置自动移动的定时器
setAutoMoveTimeout() {
if (this.randomMoveTimeout) {
this.randomMoveTimeout.clear()
}
this.randomMoveTimeout = this.clock.setTimeout(() => this.doRandomMove(), TURN_TIMEOUT * 1000)
}

// 随机移动
doRandomMove() {
const sessionId = this.state.currentTurn
for (let x=0; x<BOARD_WIDTH; x++) {
for (let y=0; y<BOARD_WIDTH; y++) {
const index = x + BOARD_WIDTH * y;
if (this.state.board[index] === 0) {
this.messageHandler({ sessionId } as Client, 'pos' ,{ x, y })
return;
}
}
}
}

// 检测当前的底板是否已经满了
checkBoardComplete() {
return this.state.board.filter(item => item === 0).length === 0;
}

// 检测是否成功
checkWin (x: number, y: number, move: number) {
let won = false
let board = this.state.board
// 横向
for(let y = 0; y < BOARD_WIDTH; y++){
const i = x + BOARD_WIDTH * y
if (board[i] !== move) { break }
if (y == BOARD_WIDTH-1) {
won = true
}
}

// 纵向
for(let x = 0; x < BOARD_WIDTH; x++){
const i = x + BOARD_WIDTH * y
if (board[i] !== move) { break }
if (x == BOARD_WIDTH-1) {
won = true;
}
}

// 交叉
if(x === y) {
for (let xy = 0; xy < BOARD_WIDTH; xy++){
const i = xy + BOARD_WIDTH * xy
if (board[i] !== move) { break }
if (xy == BOARD_WIDTH-1) {
won = true;
}
}
}

for (let x = 0; x < BOARD_WIDTH; x++) {
const y = (BOARD_WIDTH - 1) - x
const i = x + BOARD_WIDTH * y
if (board[i] !== move) { break }
if (x == BOARD_WIDTH - 1){
won = true
}
}

return won
}

onLeave (client: Client, consented: boolean) {
// 删除当前离开的玩家
delete this.state.players[ client.sessionId ]
// 清除自动移动的定时器
if (this.randomMoveTimeout) {
this.randomMoveTimeout.clear()
}
// 获取剩余玩家
let remainingPlayerIds = Object.keys(this.state.players)
if (remainingPlayerIds.length > 0) {
// 设置当前玩家为赢家
this.state.winner = remainingPlayerIds[0]
}
}

onDispose() {

}

}

客户端主要代码

  • TicTacToe.ts
import OXChess from "./OXChess";

const {ccclass, property} = cc._decorator;

const enum ResultType {
WIN,
LOSE,
DRAW
}

const enum GameState {
NON,
MATCH,
GAME,
RESULT
}

@ccclass
export default class TicTacToe extends cc.Component {
client: Colyseus.Client
room: Colyseus.Room
gameState: GameState = GameState.NON

@property() host: string = ''
@property(cc.Node) boardNode: cc.Node = null
@property(cc.Node) matchingLayer: cc.Node = null
@property(cc.Node) resultLayer: cc.Node = null
@property(cc.Label) turnLabel: cc.Label = null
@property(cc.Label) timeLabel: cc.Label = null
@property(cc.Label) resultLabel: cc.Label = null
@property(cc.Button) matchBtn: cc.Button = null
@property(cc.EditBox) nameEditBox: cc.EditBox = null
@property(cc.Label) yourName: cc.Label = null
@property(cc.Label) otherName: cc.Label = null
curTurn: string
turnTimer: any
time: number = 0
playerMap: Map<string, {name: string, id: string}> = new Map<string, {name: string, id: string}>()

start() {
this.matchBtn.node.on('click', () => {
this.resultLayer.active = false
this.matchBtn.node.active = false
this.startMatch()
})

this.boardNode.children.forEach((node: cc.Node, i: number) => {
node.on(cc.Node.EventType.TOUCH_START, () => {
if (this.gameState != GameState.GAME) {
return
}
this.room.send('pos', { x: (i % 3) , y: ~~(i / 3)})
})
})
this.client = new Colyseus.Client(this.host)
}


startMatch() {
this.gameState = GameState.MATCH
this.matchingLayer.active = true
this.nameEditBox.node.active = false
this.client.joinOrCreate('tictactoc', {name: this.nameEditBox.string}).then(room => {
this.room = room
room.state.players.onAdd = (player: any) => {
this.playerMap.set(player.id, player)
if (room.sessionId === player.id) {
this.yourName.string = player.name
} else {
this.otherName.string = player.name
}
}
room.state.players.onChange = (player: any) => {
this.playerMap.set(player.id, player)
}
room.state.players.onRemove= (player: any) => {
this.playerMap.delete(player.id)
}

room.onStateChange(state => {
if (this.gameState == GameState.MATCH && Object.keys(room.state.players).length === 2) {
this.curTurn = state.currentTurn
this.startGame()
}
if (state.draw) {
this.gameResult(ResultType.DRAW)
}
if (state.winner) {
if (this.room.sessionId === state.winner) {
this.gameResult(ResultType.WIN)
} else {
this.gameResult(ResultType.LOSE)
}
}
if (this.gameState == GameState.GAME) {
this.turnLabel.string = this.room.sessionId === state.currentTurn ? '你的回合' : '对手回合'
}
// 棋局
this.boardNode.children.forEach((node: cc.Node, i: number) => {
let chess = node.getComponent(OXChess)
if (this.gameState == GameState.GAME && chess.getCurIndex() == 0 && state.board[i] != 0) {
this.changeTurn()
}
chess.setIcon(state.board[i])
})
})
}) as any
}

startGame() {
this.gameState = GameState.GAME
this.matchingLayer.active = false

this.changeTurn()
}

gameResult(type: ResultType) {
this.gameState = GameState.RESULT
this.matchBtn.node.active = true
this.resultLayer.active = true
this.nameEditBox.node.active = true
this.yourName.string = ''
this.otherName.string = ''
this.playerMap.clear()
switch(type) {
case ResultType.WIN: {
this.resultLabel.string = '赢就是这样简单'
break
}
case ResultType.LOSE: {
this.resultLabel.string = '失败也是一种进步'
break
}
case ResultType.DRAW: {
this.resultLabel.string = '和棋是一种态度'
break
}
}
this.turnLabel.string = '未知'
this.timeLabel.string = '0'
if (this.turnTimer) {
clearInterval(this.turnTimer)
}
}

changeTurn() {
this.time = 10
this.timeLabel.string = this.time + ''
if (this.turnTimer) {
clearInterval(this.turnTimer)
}
this.turnTimer = setInterval(() => {
this.time--
if (this.time < 0) {
this.time = 0
}
this.timeLabel.string = this.time + ''
}, 1000)
}
// update (dt) {}
}
  • OXChess.ts
const {ccclass, property} = cc._decorator;

@ccclass
export default class OXChess extends cc.Component {

@property([cc.SpriteFrame])
chessIcons: cc.SpriteFrame[] = []

@property(cc.Sprite)
icon: cc.Sprite = null

curIndex: number = 0

setIcon(id: number) {
switch(id) {
case 0: {
this.icon.spriteFrame = null
break
}
case 1: {
this.icon.spriteFrame = this.chessIcons[0]
break
}
case 2: {
this.icon.spriteFrame = this.chessIcons[1]
break
}
}
this.curIndex = id
}

getCurIndex() {
return this.curIndex
}
}

五子棋

OX棋
OX棋

服务器房间主要代码

import { Room, Delayed, Client } from "colyseus";
import { type, Schema, MapSchema, ArraySchema } from '@colyseus/schema';

// 一个回合的时间
const TURN_TIMEOUT = 10
// 棋盘宽度
const BOARD_WIDTH = 13

class Player extends Schema {
@type('string')
id: string
@type('string')
name: string
}

const boardMap = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]

// 状态同步的内容
class State extends Schema {
@type('string')
currentTurn: string
@type({map: Player })
players = new MapSchema<Player>()
@type(['number'])
board: number[] = new ArraySchema<number>(...boardMap)
@type('string')
winner: string
@type('boolean')
draw: boolean
}


export class FiveRoom extends Room<State> {
maxClients = 2
randomMoveTimeout: Delayed

onCreate (options: any) {
this.setState(new State())
// 消息回调
this.onMessage("pos", this.posMessageHandler.bind(this))
this.onMessage("ai", (client: Client, message: any) => {
this.posMessageHandler(client, this.useAiMove())
})
}

posMessageHandler(client: Client, message: any) {
// 当前已经有赢家或者为平局
if (this.state.winner || this.state.draw) {
return
}
// 当前玩家的回合
if (client.sessionId === this.state.currentTurn) {
let data: any = message
const playerIds = Object.keys(this.state.players)
const index = data.x + BOARD_WIDTH * data.y
if (this.state.board[index] === 0) {
const move = (client.sessionId === playerIds[0]) ? 1 : 2
this.state.board[index] = move
if (this.checkWin(data.x, data.y, move)) {
this.state.winner = client.sessionId
} else if (this.checkBoardComplete()) {
this.state.draw = true;
} else {
// 获取另一个玩家的id
const otherPlayerSessionId = (client.sessionId === playerIds[0]) ? playerIds[1] : playerIds[0]
// 设置当前的轮回
this.state.currentTurn = otherPlayerSessionId
// 开启自动移动定时器
this.setAutoMoveTimeout()
}
}
}
}

onJoin (client: Client, options: any) {
let player = new Player()
player.id = client.sessionId
player.name = options.name || this.randomName()
this.state.players[client.sessionId] = player
if (Object.keys(this.state.players).length === 2) {
// 当前的turn
this.state.currentTurn = client.sessionId
this.setAutoMoveTimeout()
// 锁定当前的房间
this.lock()
}
}

randomName() {
return ~~(Math.random() * 10000) + ''
}

// 设置自动移动的定时器
setAutoMoveTimeout() {
if (this.randomMoveTimeout) {
this.randomMoveTimeout.clear()
}
this.randomMoveTimeout = this.clock.setTimeout(() => this.doRandomMove(), TURN_TIMEOUT * 1000)
}

useAiMove() : {x: number, y: number} {
const playerIds = Object.keys(this.state.players)
const move = (this.state.currentTurn === playerIds[0]) ? 1 : 2
let maxX = 0,
maxY = 0,
maxWeight = 0,
i, j, tem
for (i = 14; i >= 0; i--) {
for (j = 14; j >= 0; j--) {
if (this.getChessColor(i, j) !== 0) {
continue
}
tem = this.computeWeight(i, j, move)
if (tem > maxWeight) {
maxWeight = tem
maxX = i
maxY = j
}
}
}
return {x: maxX, y: maxY}
}

// 随机移动
doRandomMove() {
// 当前的id
const sessionId = this.state.currentTurn
this.posMessageHandler({ sessionId } as Client, this.useAiMove())

// for (let x=0; x<BOARD_WIDTH; x++) {
// for (let y=0; y<BOARD_WIDTH; y++) {
// const index = x + BOARD_WIDTH * y;
// if (this.state.board[index] === 0) {
// this.messageHandler({ sessionId } as Client, 'pos' ,{ x, y })
// return;
// }
// }
// }
}

//计算下子至i,j的权重
computeWeight(i: number, j: number, move: number) {
var weight = 14 - (Math.abs(i - 7) + Math.abs(j - 7)), //基于棋盘位置权重
pointInfo: any = {}, //某点下子后连子信息
chessColor = move
//x方向
pointInfo = this.putDirectX(i, j, chessColor);
weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true);//AI下子权重
pointInfo = this.putDirectX(i, j, -chessColor);
weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false);//player下子权重
//y方向
pointInfo = this.putDirectY(i, j, chessColor);
weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true);//AI下子权重
pointInfo = this.putDirectY(i, j, -chessColor);
weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false);//player下子权重
//左斜方向
pointInfo = this.putDirectXY(i, j, chessColor);
weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true);//AI下子权重
pointInfo = this.putDirectXY(i, j, -chessColor);
weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false);//player下子权重
//右斜方向
pointInfo = this.putDirectYX(i, j, chessColor);
weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true);//AI下子权重
pointInfo = this.putDirectYX(i, j, -chessColor);
weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false);//player下子权重
return weight;
}
// 下子到i,j X方向 结果: 多少连子 两边是否截断
putDirectX(i: number, j: number, chessColor: number) {
var m, n,
nums = 1,
side1 = false,
side2 = false
for (m = j - 1; m >= 0; m--) {
if (this.getChessColor(i, m) === chessColor) {
nums++;
}
else {
if (this.getChessColor(i, m) === 0) {
side1 = true;
}
break;
}
}
for (m = j + 1; m < 15; m++) {
if (this.getChessColor(i, m) === chessColor) {
nums++;
}
else {
if (this.getChessColor(i, m)=== 0) {
side2 = true;
}
break;
}
}
return {"nums": nums, "side1": side1, "side2": side2}
}

// 下子到i,j Y方向 结果
putDirectY(i: number, j: number, chessColor: number) {
let m, n,
nums = 1,
side1 = false,
side2 = false
for (m = i - 1; m >= 0; m--) {
if (this.getChessColor(m, j) === chessColor) {
nums++
} else {
if (this.getChessColor(m, j) === 0) {
side1 = true
}
break
}
}
for (m = i + 1; m < 15; m++) {
if (this.getChessColor(m, j) === chessColor) {
nums++
} else {
if (this.getChessColor(m, j) === 0) {
side2 = true
}
break
}
}
return {"nums": nums, "side1": side1, "side2": side2}
}

//下子到i,j XY方向 结果
putDirectXY(i: number, j: number, chessColor: number) {
let m, n,
nums = 1,
side1 = false,
side2 = false;
for (m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
if (this.getChessColor(m, n) === chessColor) {
nums++
} else {
if (this.getChessColor(m, n) === 0) {
side1 = true
}
break
}
}
for (m = i + 1, n = j + 1; m < 15 && n < 15; m++, n++) {
if (this.getChessColor(m, n) === chessColor) {
nums++
} else {
if (this.getChessColor(m, n) === 0) {
side2 = true
}
break
}
}
return {"nums": nums, "side1": side1, "side2": side2};
}

putDirectYX(i: number, j: number, chessColor: number) {
let m, n,
nums = 1,
side1 = false,
side2 = false
for (m = i - 1, n = j + 1; m >= 0 && n < 15; m--, n++) {
if (this.getChessColor(m, n) === chessColor) {
nums++
} else {
if (this.getChessColor(m, n) === 0) {
side1 = true
}
break;
}
}
for (m = i + 1, n = j - 1; m < 15 && n >= 0; m++, n--) {
if (this.getChessColor(m, n) === chessColor) {
nums++
} else {
if (this.getChessColor(m, n) === 0) {
side2 = true
}
break
}
}
return {"nums": nums, "side1": side1, "side2": side2}
}

// 权重方案 独:两边为空可下子,单:一边为空
weightStatus(nums: number, side1: number, side2: number, isAI: boolean) {
let weight = 0
switch (nums) {
case 1:
if (side1 && side2) {
weight = isAI ? 15 : 10; //独一
}
break
case 2:
if (side1 && side2) {
weight = isAI ? 100 : 50; //独二
}
else if (side1 || side2) {
weight = isAI ? 10 : 5; //单二
}
break
case 3:
if (side1 && side2) {
weight = isAI ? 500 : 200; //独三
}
else if (side1 || side2) {
weight = isAI ? 30 : 20; //单三
}
break
case 4:
if (side1 && side2) {
weight = isAI ? 5000 : 2000; //独四
}
else if (side1 || side2) {
weight = isAI ? 400 : 100; //单四
}
break
case 5:
weight = isAI ? 100000 : 10000; //五
break
default:
weight = isAI ? 500000 : 250000;
break
}
return weight
}

// 检测当前的底板是否已经满了
checkBoardComplete() {
return this.state.board.filter(item => item === 0).length === 0;
}

getChessColor(x: number, y: number) {
return this.state.board[x + BOARD_WIDTH * y]
}

// 检测是否成功
checkWin (i: number, j: number, move: number) : boolean {
let num = 1, chessColor = move, m = 0, n = 0
//x方向
for (m = j - 1; m >= 0; m--) {
if (this.getChessColor(i, m) === chessColor) {
num++;
}
else {
break;
}
}
for (m = j + 1; m < 15; m++) {
if (this.getChessColor(i, m) === chessColor) {
num++
}
else {
break;
}
}
if (num >= 5) {
return true
} else {
num = 1
}
//y方向
for (m = i - 1; m >= 0; m--) {
if (this.getChessColor(m, j) === chessColor) {
num++
} else {
break
}
}
for (m = i + 1; m < 15; m++) {
if (this.getChessColor(m, j) === chessColor) {
num++
} else {
break
}
}
if (num >= 5) {
return true
} else {
num = 1
}
//左斜方向
for (m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
if (this.getChessColor(m, n) === chessColor) {
num++
} else {
break
}
}
for (m = i + 1, n = j + 1; m < 15 && n < 15; m++, n++) {
if (this.getChessColor(m, n) === chessColor) {
num++
} else {
break
}
}
if (num >= 5) {
return true
} else {
num = 1
}
//右斜方向
for (m = i - 1, n = j + 1; m >= 0 && n < 15; m--, n++) {
if (this.getChessColor(m, n) === chessColor) {
num++
} else {
break
}
}
for (m = i + 1, n = j - 1; m < 15 && n >= 0; m++, n--) {
if (this.getChessColor(m, n) === chessColor) {
num++
} else {
break
}
}
if (num >= 5) {
return true
}
return false
}

onLeave (client: Client, consented: boolean) {
// 删除当前离开的玩家
delete this.state.players[ client.sessionId ]
// 清除自动移动的定时器
if (this.randomMoveTimeout) {
this.randomMoveTimeout.clear()
}
// 获取剩余玩家
let remainingPlayerIds = Object.keys(this.state.players)
if (remainingPlayerIds.length > 0) {
// 设置当前玩家为赢家
this.state.winner = remainingPlayerIds[0]
}
}

onDispose() {

}

}

客户端主要代码

  • FiveGame.ts
import FiveChess from "./FiveChess";

const {ccclass, property} = cc._decorator;

const enum ResultType {
WIN,
LOSE,
DRAW
}

const enum GameState {
NON,
MATCH,
GAME,
RESULT
}

@ccclass
export default class FiveGame extends cc.Component {

client: Colyseus.Client
room: Colyseus.Room
gameState: GameState = GameState.NON

@property() host: string = ''
@property(cc.Node) boardNode: cc.Node = null
@property(cc.Node) matchingLayer: cc.Node = null
@property(cc.Node) resultLayer: cc.Node = null
@property(cc.Label) turnLabel: cc.Label = null
@property(cc.Label) timeLabel: cc.Label = null
@property(cc.Label) resultLabel: cc.Label = null
@property(cc.Button) matchBtn: cc.Button = null
@property(cc.EditBox) nameEditBox: cc.EditBox = null
@property(cc.Label) yourName: cc.Label = null
@property(cc.Label) otherName: cc.Label = null
@property(cc.Button) aiButton: cc.Button = null
curTurn: string
turnTimer: any
time: number = 0
playerMap: Map<string, {name: string, id: string}> = new Map<string, {name: string, id: string}>()
useAi: boolean = false

start () {

this.aiButton.node.on('click', () => {
this.room.send('ai')
})

this.matchBtn.node.on('click', () => {
this.resultLayer.active = false
this.matchBtn.node.active = false
this.startMatch()
})

this.boardNode.children.forEach((node: cc.Node, i: number) => {
node.on(cc.Node.EventType.TOUCH_START, () => {
console.log('点击')
if (this.gameState != GameState.GAME) {
return
}
this.room.send('pos', { x: (i % 13) , y: ~~(i / 13)})
})
})
this.client = new Colyseus.Client(this.host)
this.cleanBoard()
}

cleanBoard() {
this.boardNode.children.forEach((node: cc.Node) => {
node.getComponent(FiveChess).setIcon(0)
})
}

startMatch() {
this.gameState = GameState.MATCH
this.matchingLayer.active = true
this.nameEditBox.node.active = false
this.useAi = false
if (this.nameEditBox.string == 'joker') {
this.useAi = true
}
this.client.joinOrCreate('five', {name: this.nameEditBox.string}).then(room => {
this.room = room
room.state.players.onAdd = (player: any) => {
this.playerMap.set(player.id, player)
if (room.sessionId === player.id) {
this.yourName.string = player.name
} else {
this.otherName.string = player.name
}
}
room.state.players.onChange = (player: any) => {
this.playerMap.set(player.id, player)
}
room.state.players.onRemove= (player: any) => {
this.playerMap.delete(player.id)
}

room.onStateChange(state => {
if (this.gameState == GameState.MATCH && Object.keys(room.state.players).length === 2) {
this.curTurn = state.currentTurn
this.startGame()
}
if (state.draw) {
this.gameResult(ResultType.DRAW)
}
if (state.winner) {
if (this.room.sessionId === state.winner) {
this.gameResult(ResultType.WIN)
} else {
this.gameResult(ResultType.LOSE)
}
}
if (this.gameState == GameState.GAME) {
this.turnLabel.string = this.room.sessionId === state.currentTurn ? '你的回合' : '对手回合'
}
// 棋局
this.boardNode.children.forEach((node: cc.Node, i: number) => {
let chess = node.getComponent(FiveChess)
if (this.gameState == GameState.GAME && chess.getCurIndex() == 0 && state.board[i] != 0) {
this.changeTurn()
}
chess.setIcon(state.board[i])
})
})
}) as any
}

startGame() {
this.aiButton.node.active = this.useAi
this.gameState = GameState.GAME
this.matchingLayer.active = false
this.changeTurn()
}

gameResult(type: ResultType) {
this.gameState = GameState.RESULT
this.matchBtn.node.active = true
this.resultLayer.active = true
this.nameEditBox.node.active = true
this.aiButton.node.active = false
this.yourName.string = ''
this.otherName.string = ''
this.playerMap.clear()
switch(type) {
case ResultType.WIN: {
this.resultLabel.string = '赢就是这样简单'
break
}
case ResultType.LOSE: {
this.resultLabel.string = '失败也是一种进步'
break
}
case ResultType.DRAW: {
this.resultLabel.string = '和棋是一种态度'
break
}
}
this.turnLabel.string = '未知'
this.timeLabel.string = '0'
if (this.turnTimer) {
clearInterval(this.turnTimer)
}
}

changeTurn() {
this.time = 10
this.timeLabel.string = this.time + ''
if (this.turnTimer) {
clearInterval(this.turnTimer)
}
this.turnTimer = setInterval(() => {
this.time--
if (this.time < 0) {
this.time = 0
}
this.timeLabel.string = this.time + ''
}, 1000)
}
}
  • FiveChess.ts
const {ccclass, property} = cc._decorator;

@ccclass
export default class FiveChess extends cc.Component {

@property([cc.SpriteFrame])
chessIcons: cc.SpriteFrame[] = []

@property(cc.Sprite)
icon: cc.Sprite = null

curIndex: number = 0

setIcon(id: number) {
switch(id) {
case 0: {
this.icon.spriteFrame = null
break
}
case 1: {
this.icon.spriteFrame = this.chessIcons[1]
break
}
case 2: {
this.icon.spriteFrame = this.chessIcons[0]
break
}
}
this.curIndex = id
}

getCurIndex() {
return this.curIndex
}
}

mosca

安装

npm i mosca
npm i mqtt

简单服务器

server.ts

import { Server, Client, Packet, Authorizer } from 'mosca'

// 简单的设置方法
const setting = {
// 监听的端口
port: 12345,
// 开启http的客户端
http: {
port: 12346, // http的绑定端口
bundle: true, // 提供mqtt.js文件地址
}
}

const server = new Server(setting)
const auth = new Authorizer()


server.on('clientConnected', (client: Client) => {
console.log('客户端连接的ID:', client.id)
})

server.on('clientDisconnecting', (client: Client) => {
console.log('客户端正在断连', client.id)
})

server.on('clientDisconnected', (client: Client) => {
console.log('客户端已经断连', client.id)
})

server.on('published', (packet: Packet, client: Client) => {
console.log('发布消息:', packet)
})

server.on('subscribed', (topic, client: Client) => {
console.log('订阅主题:', topic)
})

server.on('unsubscribed', (topic, client: Client) => {
console.log('取消订阅:', topic)
})

server.on('ready', () => {
console.log('服务器启动成功!')
// 简单的acl版本
// server.authenticate = auth.authenticate
// auth.addUser('limo', 'limo1234', '+', '+', (err) => {})

// 自定义的校验方法
server.authenticate = (client: Client, username: string, password: string, callback) => {
if (username == 'limo' && password == 'limo123') {
return callback(null, true)
} else {
return callback(null, false)
}
}

server.authorizePublish = (client: Client, topic: string, payload: string, callback) => {
return callback(null, true)
}

server.authorizeSubscribe = (client: Client, topic: string, callback) => {
return callback(null, true)
}
});

简单的本地客户端

client.ts

import { connect, Client } from 'mqtt'

const client: Client = connect('mqtt://127.0.0.1:12345', {
username: 'limo',
password: 'limo123'
})

const topic = 'sys/log/now'

client.on('connect', () => {
console.log('客户端连接成功!')
client.subscribe(topic)
setInterval(() => {
client.publish(topic, new Date().toLocaleString())
}, 1000)
})

client.on('error', (err) => {
console.log('连接失败!')
client.end()
})

client.on('message', (topic, message) => {
console.log(`topic:${topic} message:${message}`)
})

简单的html客户端

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./mqtt.js"></script>
</head>
<body>
<script >
const client = mqtt.connect('mqtt://192.168.218.88:12346')
const topic = 'sys/log/now'
client.on('connect', () => {
console.log('客户端连接成功!')
client.subscribe(topic)
})
client.on('message', (topic, message) => {
console.log(`topic:${topic} message:${message}`)
})
</script>
</body>
</html>

redpoint

游戏中会有许多操作的按钮需要有提示,引导用户点击,实现游戏内的操作

红点的类

  • 使用枚举的选择框来确定红点
  • 挂载在红点的节点
  • 节点下需要挂载一个用来作为红点的icon
import { RedPointKey, RedPointCalKey, RED_POINT_MESSAGE } from "./RedPointEnum";
import { RedPointControl } from "./RedPointControl";

const {ccclass, property} = cc._decorator;

@ccclass
export default class RedPoint extends cc.Component {
// 红点条件key值
@property({type: [cc.Enum(RedPointKey)], tooltip:"红点条件key值"})
public keyArr:Array<RedPointKey> = []

// 红点计算key值
@property({type: [cc.Enum(RedPointCalKey)], tooltip:"红点计算key值"})
public CalKeyArr: Array<RedPointCalKey> = []

// 红点图片
private redSprite:cc.Node = null;

onLoad() {
this.redSprite = this.node.getChildByName('redSprite')
if(!this.redSprite) {
this.redSprite = this.node
}
// 绑定监听
cc.director.on(RED_POINT_MESSAGE, (calKey: RedPointCalKey) => {
for(let v in this.CalKeyArr) {
if (this.CalKeyArr[v] == calKey) {
this.updateUI()
return
}
}
})
}

// 当界面的红点数据已经存在的时候,进入界面使用红点数据
onEnable() {
this.updateUI()
}

updateUI() {

for (let v in this.keyArr) {
let redKey = this.keyArr[v]
let isShow = RedPointControl.getInstance().isShow(redKey)
if (isShow) {
this.redSprite.active = true
return
}
}

this.redSprite.active = false;
}

}

红点的枚举类

  • 有新的红点位置需要在RedPointKey中注册
  • 然后定义RedPointCalKey计算相关的一系列红点
  • 一个PointKey对应一个红点
/**
* 红点的key
*
* @export
* @enum {number}
*/
export enum RedPointKey {
MIN = 0,
k_BUTTON_1,
k_BUTTON_2,
k_BUTTON_3
}

/**
* 红点的计算的key
*
* @export
* @enum {number}
*/
export enum RedPointCalKey {
MIN = 0,
ck_TEST_BUTTON
}

// 红点的消息
export let RED_POINT_MESSAGE = 'RED_POINT_MESSAGE'

红点的控制类

  • 用来计算红点的显示
  • 存储红点的信息

import { RedPointKey, RedPointCalKey, RED_POINT_MESSAGE } from "./RedPointEnum";
import { GameData } from "../script/util/GameData";

export class RedPointControl {
private static _instance: RedPointControl
private constructor() {}
static getInstance() : RedPointControl {
if (!RedPointControl._instance) {
RedPointControl._instance = new RedPointControl()
}
return RedPointControl._instance
}

private pointData: Map<RedPointKey, boolean> = new Map<RedPointKey, boolean>()

/**
* 当前红点是否显示
*
* @param {RedPointKey} key
* @returns {boolean}
* @memberof RedPointControl
*/
public isShow(key: RedPointKey) : boolean {
return this.pointData.get(key)
}

/**
* 设置当前的红点数据
*
* @private
* @param {RedPointKey} key
* @param {boolean} isShow
* @memberof RedPointControl
*/
private setPointData(key: RedPointKey, isShow: boolean) {
this.pointData.set(key, isShow)
}

/**
* 计算红点的方法类
*
* @param {RedPointCalKey} calKey
* @memberof RedPointControl
*/
public cal(calKey: RedPointCalKey) {
switch(calKey) {
case RedPointCalKey.ck_TEST_BUTTON: {
this.setPointData(RedPointKey.k_BUTTON_1, GameData.getInstance().getFlag())
this.setPointData(RedPointKey.k_BUTTON_2, GameData.getInstance().getFlag2())
this.setPointData(RedPointKey.k_BUTTON_3, true)
}
}

cc.director.emit(RED_POINT_MESSAGE, calKey)
}

}

数据更新

  • 在游戏内数据有更新的时候,通过对应的计算的key计算红点数据
  • 不同的数据使用不同的key计算
RedPointControl.getInstance().cal(RedPointCalKey.ck_TEST_BUTTON)

BubbleSort

  • 冒泡排序
/**
* 冒泡排序
*
* @param {Array<number>} array
* @returns {Array<number>}
*/
function BubbleSort(array: Array<number>) : Array<number> {
let retArray = [].concat(array)
for (let i = 0, n = retArray.length; i < n; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (retArray[j] > retArray[j + 1]) {
let temp = retArray[j]
retArray[j] = retArray[j + 1]
retArray[j + 1] = temp
}
}
}
return retArray
}

ChooseSort

  • 选择排序
/**
* 选择排序
*
* @param {Array<number>} array
* @returns {Array<number>}
*/
function ChooseSort(array: Array<number>) : Array<number> {
let retArray = [].concat(array)
let min = 0, key = 0
for (let i = 0, n = retArray.length; i < n; i++) {
min = retArray[i]
key = i
for (let j = i + 1; j < n; j++) {
if (min > retArray[j]) {
key = j
min = retArray[j]
}
}
if (key != i) {
let temp = retArray[i]
retArray[i] = retArray[key]
retArray[key] = temp
}
}
return retArray
}

InsertSort

  • 插入排序
/**
* 插入排序
*
* @param {Array<number>} array
* @returns {Array<number>}
*/
function InsertSort(array: Array<number>) : Array<number> {
let retArray = [].concat(array)
for (let i = 1, n = retArray.length; i < n; i++ ) {
let temp = retArray[i]
let j = i
while(j > 0 && retArray[j - 1] > temp) {
retArray[j] = retArray[j - 1]
j--
}
retArray[j] = temp
}
return retArray
}

ShellSort

  • 希尔排序
/**
* 希尔排序
*
* @param {Array<number>} array
* @returns {Array<number>}
*/
function ShellSort(array: Array<number>) : Array<number> {
let retArray = [].concat(array)
let n = retArray.length
let gap = ~~(n / 2)
while (gap >= 1) {
for (let i = gap; i < n; i++) {
let temp = retArray[i]
let j = i
while(j > gap - 1 && retArray[j - gap] > temp) {
retArray[j] = retArray[j - gap]
j -= gap
}
retArray[j] = temp
}
gap = ~~(gap / 2)
}
return retArray
}

QuickSort

  • 快速排序
/**
* 快速排序
*
* @param {Array<number>} array
* @returns {Array<number>}
*/
function QuickSort(array: Array<number>) : Array<number> {
let retArray = [].concat(array)
let n = retArray.length
/**
* 快速排序的递归方法
*
* @param {number} left
* @param {number} right
*/
function quick(begin: number, end: number) {

//递归出口
if (begin >= end) {
return
}
let l = begin // 左指针
let r = end // 右指针
let temp = retArray[l] // 基准数,这里取数组第一个数
// 左右指针相遇的时候退出扫描循环
while(l < r) {
// 右指针从右向左扫描,碰到第一个小于基准数的时候停住
while(l < r && retArray[r] >= temp) {
r--
}
// 左指针从左向右扫描,碰到第一个大于基准数的时候停住
while(l < r && retArray[l] <= temp) {
l++
}
// 交换左右指针所停位置的数
[retArray[l], retArray[r]] = [retArray[r], retArray[l]]
}
// 最后交换基准数与指针相遇位置的数
[retArray[begin], retArray[l]] = [retArray[l], retArray[begin]]
// 递归处理左右数组
quick(begin, l - 1)
quick(l + 1, end)
}
quick(0, n - 1)
return retArray
}

时间测试

  • 使用含5000个随机整数的数组测试排序效率
let testArr = []
let len = 5000
let time = Date.now()
for (let i = 0; i < len; i++) {
testArr.push(~~(Math.random() * len))
}
// 答案
let resultArr = testArr.sort((a: number, b: number) => a - b)

// 冒泡排序
time = Date.now()
let ans1 = BubbleSort(testArr)
if (JSON.stringify(resultArr) === JSON.stringify(ans1)) {
console.log('BubbleSort Cost', Date.now() - time, 'ms')
}

// 选择排序
time = Date.now()
let ans2 = ChooseSort(testArr)
if (JSON.stringify(resultArr) === JSON.stringify(ans2)) {
console.log('ChooseSort Cost', Date.now() - time, 'ms')
}

// 插入排序
time = Date.now()
let ans3 = InsertSort(testArr)
if (JSON.stringify(resultArr) === JSON.stringify(ans3)) {
console.log('InsertSort Cost', Date.now() - time, 'ms')
}

// 希尔排序
time = Date.now()
let ans4 = ShellSort(testArr)
if (JSON.stringify(resultArr) === JSON.stringify(ans4)) {
console.log('ShellSort Cost', Date.now() - time, 'ms')
}

// 快速排序
time = Date.now()
let ans5 = QuickSort(testArr)
if (JSON.stringify(resultArr) === JSON.stringify(ans5)) {
console.log('QuickSort Cost', Date.now() - time, 'ms')
}