C语言随机函数与随机种子

C语言随机函数rand(),使用该函数会产生0-RAND_MAX (0x7fffffff)之间的随机数,直接这么使用有个很大的问题 程序每次运行产生的随机数序列都是相同的 (这里指的是随机序列,并不是指rand()每次结果相同)

例如:

#include <iostream>

int main() {
    for (int i = 0; i < 10; i++) {
            std::cout << rand() << ",";
    }
    return 0;
}

第一次输出结果为: 16807,282475249,1622650073,984943658,1144108930,470211272,101027544,1457850878,1458777923,2007237709,
第二次输出结果为: 16807,282475249,1622650073,984943658,1144108930,470211272,101027544,1457850878,1458777923,2007237709,

无论运行多少次,我们会发现输出结果都是一致的,原因是因为没有指定随机种子,系统会默认随机种子为1

执行下面代码

#include <iostream>

int main() {
    srand(1);
    for (int i = 0; i < 10; i++) {
            std::cout << rand() << ",";
    }
    return 0;
}

输出结果也是:16807,282475249,1622650073,984943658,1144108930,470211272,101027544,1457850878,1458777923,2007237709,
所以说为了保证程序每次产生的随机数不同,需要保证程序每次启动的随机种子不同,通常可以使用系统时间作为随机种子time(NULL)
这个随机序列和电脑相关的,不同的电脑即使随机种子一致,产生的随机序列也是不一致的。换了一台电脑同样的代码输出的结果是:41,18467,6334,26500,19169,15724,11478,29358,26962,24464,

#include <iostream>

int main() {
    srand(time(NULL));
    for (int i = 0; i < 10; i++) {
            std::cout << rand() << ",";
    }
    return 0;
}

第一次输出结果为:1784895276,535838789,1443594852,228433758,1732893517,568119605,673906673,520698833,389424706,1678361333,
第二次输出结果为:1785550749,814955265,302438289,2134014421,1255985200,1726490037,319013595,1542308253,1447188881,503737045,

函数说明

srand 函数用来设置随机种子
rand 函数用来生成随机数

C语言数组加取地址符(&)和不加取地址符(&)的区别

实践出真知,直接上代码:

#include <iostream>

int main() {
    short s[4] = {0x01, 0x21, 0x32, 0x53};
    printf("&s的值:%u\n", &s);
    printf("s的值:%u\n", s);
    printf("&s+1的值:%u\n", &s + 1);
    printf("s+1的值:%u\n", s + 1);
    printf("&s[1]的值:%u\n", &s[1]);
    printf("s[1]的值:%u\n", s[1]);
    return 0;
}

输出结果:
&s的值:3805102112
s的值:3805102112
&s+1的值:3805102120
s+1的值:3805102114
&s[1]的值:3805102114
s[1]的值:33

得出结论:
&s数组首地址
s数组首地址
&s+1数组首地址 + sizeof(short) * 元素个数
s + 1数组首地址+ sizeof(short),第二个元素的地址
&s[1]数组第二个元素的地址
s[1]数字元素的值

IOS使用比特币 secp256k1 库实现椭圆曲线算法

下载https://github.com/beyonderyue/ios-secp256k1项目代码或者使用Git下载

git clone https://github.com/beyonderyue/ios-secp256k1.git

打开终端执行 ./secp256k1.sh

完成之后会在项目目录的framework文件夹下生成secp256k1.framework,把secp256k1.framework导入项目

最后引入对应的头文件:

#import <secp256k1/secp256k1.h>

 

一、生成32字节(256位)的私钥

uint8_t seed[32];
randomUint8(seed, sizeof(seed));//随机生成私钥

二、创建secp256k1_context与验证私钥

secp256k1_context *context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
secp256k1_context_randomize(context, seed);  //随机化
secp256k1_ec_seckey_verify(context, seed);   //验证私钥是否有效

三、创建根据私钥生成公钥

secp256k1_pubkey p_pubkey;
memset(&p_pubkey, 0, sizeof(p_pubkey));
secp256k1_ec_pubkey_create(context, &p_pubkey, seed);  

四、使用私钥签名

secp256k1_ecdsa_sign(context, &sig, hash, seed, secp256k1_nonce_function_rfc6979,  nullptr);
实际是对数据的sha256哈希值签名

五、使用公钥校验签名

secp256k1_ecdsa_verify(context, &sig, hash, &p_pubkey);

六、序列化公钥

unsigned char result[65] = {0};
size_t clen = sizeof(result);
secp256k1_ec_pubkey_serialize(context, result, &clen, &p_pubkey, SECP256K1_EC_COMPRESSED); 

七、反序列化公钥

secp256k1_pubkey p_a_pubkey;
secp256k1_ec_pubkey_parse(context, &p_a_pubkey, result, clen);

八、序列化签名摘要

unsigned char vchSig[72];
size_t nSigLen = sizeof(vchSig);
secp256k1_ecdsa_signature_serialize_der(context, vchSig, &nSigLen, &sig);

[视频] Windows开发环境安装与搭建

课程详情:https://www.xiyoufang.com/cocos-video

工具介绍:

  • Cocos2d-X V3.17游戏引擎
  • Android-Studio 安卓集成开发环境
  • Genymotion 安卓虚拟机
  • Cocostudio 游戏UI制作工具

最佳实践:Windows实际开发可以使用 Visual Studio 编译 Windows版本的游戏,这样因为不用通过虚拟机,开发速度会快很多,开发中偶尔用Android-Studio调试看看Android兼容性就行。

 

最新版本Cocos2d-X V3.17开发环境搭建

主要以macOS操作进行说明,Windows系统也类似,无非就是1、配置环境变量,2、使用cocos命令创建项目,3、使用IDE工具打开项目,4、编译调试运行

下载Cocos2d-X V3.17

下载链接地址:http://cocos2d-x.org/filedown/cocos2d-x-3.17.zip;下载成功后,解压到指定目录。

配置环境变量

在终端切换到cocos2d-x-3.17/目录,执行 setup.py ,将自动配置好Cocos环境变量,python的版本要求是2.7哦,python3会报错
大致会添加类似下面的环境变量

# Add environment variable COCOS_X_ROOT for cocos2d-x
export COCOS_X_ROOT=/Applications/Cocos/Cocos2d-x
export PATH=$COCOS_X_ROOT:$PATH
# Add environment variable COCOS_CONSOLE_ROOT for cocos2d-x
export COCOS_CONSOLE_ROOT=/Users/farmer/app/cocos2d-x/cocos2d-x-3.17/tools/cocos2d-console/bin
export PATH=$COCOS_CONSOLE_ROOT:$PATH
# Add environment variable COCOS_TEMPLATES_ROOT for cocos2d-x
export COCOS_TEMPLATES_ROOT=/Users/farmer/app/cocos2d-x/cocos2d-x-3.17/templates
export PATH=$COCOS_TEMPLATES_ROOT:$PATH
# Add ANT_ROOT
export ANT_ROOT=/Users/farmer/app/apache-ant-1.9.4/bin
export PATH=$ANT_ROOT:$PATH
# Add ANDROID_SDK_ROOT
#export ANDROID_SDK_ROOT=/Users/farmer/android-sdks
export ANDROID_SDK_ROOT=/Users/farmer/android-sdk-cocos2dx
export PATH=$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/tools:$PATH
# Add NDK_ROOT
export NDK_ROOT=/Users/farmer/android-ndk-r10d
export PATH=$NDK_ROOT:$PATH

 创建个C++游戏

执行

cocos new -p com.xiyoufang.mahjong -l=cpp cocostudio-mahjong

将根据模板在当前目录下创建一个C++语言开发的游戏项目。

proj开头的文件夹,包括了不同平台下的开发和编译配置,如果用Xcode、AppCode开发打开 proj.ios_mac项目,如果使用VS开发,则打开 proj.win32 项目,使用android studio开发则打开 project.android。

IOS平台

用Xcode运行这个项目看看,第一次运行因为要编译整个cocos2d时间会比较长,之后再次执行就快很多了。

一碗粥的功夫过后,游戏就跑起来了

Android平台

各种配置详情参照官方文档:http://www.cocos2d-x.org/docs/cocos2d-x/en/installation/Android-Studio.html

务必要注意android SDK 与 NDK 版本,版本太高或者太低都可能会引起错误,如果是Cocos2d-x V3.16 及以下的版本,需要用android SDK 23.0.1,更高的版本SDK移除了相关的工具,会导致无法编译。

使用android studio 打开项目,然后直接编译运行,模拟器我选择的是Genymotion(发现不能debug只能运行,为了省事最后直接真机)

经过十几分钟的漫长等待终于编译并且启动成功!最好使用真机进行调试(小米5S真机可以debug没问题)。

 

IOS的编译速度会比用安卓编译速度块很多,所以一般会主要以IOS平台的开发调试为主,需要做跨平台适配时候再用安卓调试。

Cocos2d-X基于百度SDK实现定位功能(IOS、Android双平台)

不管是游戏还是普通APP,经常会有用定位功能,这里我以Cocos2d-X为例基于百度地图SDK实现定位功能,其他APP其实也是类似方式。

申请百度地图AK

https://lbsyun.baidu.com/ 进入百度地图开放平台,注册并且创建应用,创建成功后可以获取到AK,不同的平台需要多次申请

IOS平台

导入百度SDK相关框架,并且在项目的 Info.plist 添加定位权限申请,具体可以参考百度SDK文档https://lbsyun.baidu.com/index.php?title=ios-locsdk/guide/create-project/manual-create

在AppController.h 文件中,添加百度定位相关的 Delegate,并且加上一些相关的成员变量,请忽略代码中的微信API:

#import <UIKit/UIKit.h>
#import "WXApi.h"
#import <BaiduMapAPI_Base/BMKBaseComponent.h>
#import <BaiduMapAPI_Base/BMKMapManager.h>
#import <BaiduMapAPI_Location/BMKLocationService.h>
#import <BaiduMapAPI_Search/BMKGeocodeSearch.h>

@class RootViewController;

@interface AppController : NSObject <UIApplicationDelegate,WXApiDelegate, BMKLocationServiceDelegate ,BMKGeoCodeSearchDelegate> {
    UIWindow *window;
    BMKMapManager* _mapManager;
    BMKLocationService* _locService;
    BMKGeoCodeSearch *_geocodesearch;
}

@property(nonatomic, readonly) RootViewController* viewController;

@end

在 AppController.mm中的didFinishLaunchingWithOptions中初始化百度地图服务

    //百度地图
    _mapManager = [[BMKMapManager alloc] init];
    BOOL ret = [_mapManager start:@BD_APPKEY generalDelegate:nil];
    _geocodesearch = [[BMKGeoCodeSearch alloc] init];
    _geocodesearch.delegate = self;
    if (!ret) {
        NSLog(@"manager start failed!");
    }
    _locService = [[BMKLocationService alloc] init];//初始化BMKLocationService
    _locService.delegate = self;
    [_locService startUserLocationService];//启动LocationService
//实现相关delegate 处理位置信息更新
//处理方向变更信息
- (void)didUpdateUserHeading:(BMKUserLocation *)userLocation {
    NSLog(@"heading is %@", userLocation.heading);
}

//处理位置坐标更新
- (void)didUpdateBMKUserLocation:(BMKUserLocation *)userLocation {
    NSLog(@"didUpdateUserLocation lat %f,long %f", userLocation.location.coordinate.latitude, userLocation.location.coordinate.longitude);
    BMKReverseGeoCodeOption *reverseGeocodeSearchOption = [[BMKReverseGeoCodeOption alloc] init];
    reverseGeocodeSearchOption.reverseGeoPoint = userLocation.location.coordinate;
    BOOL flag = [_geocodesearch reverseGeoCode:reverseGeocodeSearchOption];
    if (flag) {
        NSLog(@"反geo检索发送成功");
        [_locService stopUserLocationService];
        onLocationInfo(userLocation.location.coordinate.longitude, userLocation.location.coordinate.latitude);
    } else {
        NSLog(@"反geo检索发送失败");
    }
    [reverseGeocodeSearchOption release];
}

//定位结果
- (void)onGetReverseGeoCodeResult:(BMKGeoCodeSearch *)searcher result:(BMKReverseGeoCodeResult *)result errorCode:(BMKSearchErrorCode)error {
    NSLog(@"address:%@----%@", result.addressDetail, result.address);
    if (result != nil) {
        onAddressInfo([result.address cStringUsingEncoding:NSUTF8StringEncoding]);
    } else {
        //NSString *msg = @"定位失败!";
    }
}

//定位失败
- (void)didFailToLocateUserWithError:(NSError *)error {
    NSLog(@"error:%@", error);
    //NSString *msg = @"定位失败!";
}

通过自定义的onLocationInfo函数,和onAddressInfo 两个静态函数将具体的经纬度与物理地址,赋值给Cocos2d-X逻辑里的定位相关对象,方便后续IOS与ANDROID统一处理。

ANDROID平台

第一步同样是导入百度SDK相关的各种包和申请权限,具体如何导出与申请那些权限查看百度SDK文档https://lbsyun.baidu.com/index.php?title=android-locsdk/guide/create-project/eclipse

准备工作搞定后,AppActivity中添加定位代码,在onCreate中初始化百度地图服务

	mLocationClient = new LocationClient(getApplicationContext());// 声明LocationClient类
	initLocation();
	mLocationClient.registerLocationListener(myListener);
	mLocationClient.start();
/**
	 * 初始化定位配置
	 */
	private void initLocation() {
		LocationClientOption option = new LocationClientOption();
		option.setLocationMode(LocationMode.Hight_Accuracy);
		option.setCoorType("bd09ll");
		option.setScanSpan(0);
		// 可选,默认0,即仅定位一次,设置发起定位请求的间隔需要大于等于1000ms才是有效的
		option.setIsNeedAddress(true);
		// 可选,设置是否需要地址信息,默认不需要
		option.setOpenGps(true);
		// 可选,默认false,设置是否使用gps
		option.setLocationNotify(true);
		// 可选,默认false,设置是否当GPS有效时按照1S/1次频率输出GPS结果
		option.setIsNeedLocationDescribe(true);
		// 可选,默认false,设置是否需要位置语义化结果,可以在BDLocation.getLocationDescribe里得到,结果类似于“在北京天安门附近”
		option.setIsNeedLocationPoiList(true);
		// 可选,默认false,设置是否需要POI结果,可以在BDLocation.getPoiList里得到
		option.setIgnoreKillProcess(false);
		// 可选,默认true,定位SDK内部是一个SERVICE,并放到了独立进程,设置是否在stop的时候杀死这个进程,默认不杀死
		option.SetIgnoreCacheException(false);
		// 可选,默认false,设置是否收集CRASH信息,默认收集
		option.setEnableSimulateGps(false);
		// 可选,默认false,设置是否需要过滤GPS仿真结果,默认需要
		mLocationClient.setLocOption(option);
	}
	/**
	 * 定位
	 */
	public BDLocationListener myListener = new BDLocationListener() {

		@Override
		public void onReceiveLocation(final BDLocation location) {
			boolean isOk = false;
			// 获取定位结果
			StringBuffer sb = new StringBuffer(256);
			sb.append("time : ");
			sb.append(location.getTime()); // 获取定位时间
			sb.append("\nerror code : ");
			sb.append(location.getLocType()); // 获取类型类型
			sb.append("\nlatitude : ");
			sb.append(location.getLatitude()); // 获取纬度信息
			sb.append("\nlontitude : ");
			sb.append(location.getLongitude()); // 获取经度信息
			sb.append("\nradius : ");
			sb.append(location.getRadius()); // 获取定位精准度
			if (location.getLocType() == BDLocation.TypeGpsLocation) {// GPS定位结果
				isOk = true;
				sb.append("gps定位成功");
				address = location.getAddrStr(); // 地址
			} else if (location.getLocType() == BDLocation.TypeNetWorkLocation) {// 网络定位结果
				isOk = true;
				sb.append("网络定位成功");
				address = location.getAddrStr(); // 地址
			} else if (location.getLocType() == BDLocation.TypeOffLineLocation) {
				address = "离线定位失败"; // 地址
			} else if (location.getLocType() == BDLocation.TypeServerError) {
				address = "服务端网络定位失败"; // 地址
			} else if (location.getLocType() == BDLocation.TypeNetWorkException) {
				address = "网络不同导致定位失败,请检查网络是否通畅";
			} else if (location.getLocType() == BDLocation.TypeCriteriaException) {
				address = "无法获取有效定位依据导致定位失败";
			}
			sb.append(address);
			Log.i("BaiduLocationApiDem", sb.toString());
			mLocationClient.stop();
			if(isOk){
				JniHelper.onAddressInfo(location.getLatitude(), location.getLongitude(), address);
			}
		}
		
		@Override
		public void onConnectHotSpotMessage(String arg0, int arg1) {

		}
	};

到这里已经拿到了定位信息了,通过JniHelper.onAddressInfo方法将定位结果通过JNI方式传递到Cocos2d-X的C++代码层,方式也很简单JAVA层定义个Native方法

public static native void onAddressInfo(double latitude, double longitude, String address);	

C++层定一个对应的JNI函数


#if ((CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID))
#include <jni.h>
#include "platform/android/jni/JniHelper.h" 
#include <android/log.h>
extern "C"{    
	void Java_net_yw365_jni_JniHelper_onAddressInfo(JNIEnv *env,jdouble latitude, jdouble longitude, jstring address)
	{
        GetJni()->m_Latitude = latitude;
        GetJni()->m_Longitude =longitude;
        GetJni()->m_Address = cocos2d::StringUtils::getStringUTFCharsJNI(env,address);
	}
};
#endif

这样就将IOS与ANDROID平台的定位结果全部放到 m_Latitude,m_Longitude,m_Address三个变量中,后续业务逻辑与平台无关了。

无需ROOT 微信小游戏、小程序反编译

工具准备:Charles 、手机微信

第一步、Charles设置代理功能,同时启用SSL代理,Charles的详情下载配置 自行百度。

第二步、将手机与电脑在同一个局域网,并且将WIFI代理设置设置成电脑IP,端口为上图端口,

第三步、打开微信先删除需要抓取的小程序,在搜索重新添加,可以抓取到下面这类数据包,将响应另存到文件中。

根据响应报文信息分析,很轻松就能发现其中的奥秘,文件格式规则如下:

元数据:总长18BYTE;1BYTE未知 + 4BYTE(未知)+ 4BYTE(文件名定义大小) +4BYTE(文件内容大小) + 1BYTE(未知) + 4BYTE(文件数量)
文件定义:4BYTE(文件名长度) + 文件名称内容 + 4BYTE偏移量(内容起始位置) + 4BYTE长度(内容长度)
文件内容:偏移量+长度

最后根据格式用JAVA写了个解压工具: https://gitee.com/xiyoufang/wxapkg

Cocostudio 斗地主滑动选牌实现

首先上效果图

这个怎么实现的呢,其实就是通过 TouchEvent 事件实现;
ui::Widget::TouchEventType::BEGAN
ui::Widget::TouchEventType::MOVED
ui::Widget::TouchEventType::CANCELED
ui::Widget::TouchEventType::ENDED
协调这4个类型处理,主要逻辑在TouchEventType::MOVED中;通过getTouchBeganPosition获取到起始点,通过getTouchMovePosition获取到当前移动到的位置,统一使用 当前位置的 Y轴,X轴在两点之间每隔10.0f生成一个检测点,检测该点是否存在扑克牌。判断函数代码主要如下:

bool UIHelper::IsSpriteInTouch(cocos2d::Vec2 kWorldPos, cocos2d::Node *pSrite)
{
    kWorldPos = pSrite->convertToNodeSpace(kWorldPos);      //转换成节点坐标
    cocos2d::Size elementSize = pSrite->getContentSize();
    cocos2d::Rect elementRect;
    cocos2d::Vec2 kAnchor = pSrite->getAnchorPoint();
    elementRect = cocos2d::Rect(
            0,0,
            elementSize.width,
            elementSize.height
    );
    return elementRect.containsPoint(kWorldPos);
}

pSrite 为扑克牌的指针,首先将触摸点的坐标,转换成相对于扑克节点的坐标,通过扑克牌的区域构建检测区域,通过containsPoint函数判断检测点是否在扑克牌的区域内。
这里需要注意的是斗地主 除了最后一张扑克以外有很大一部分被遮挡,所有需要根据遮挡部位调整检测的区域。

bool UIHelper::IsSpriteInTouch(cocos2d::Vec2 kWorldPos, cocos2d::Node *pSrite,cocos2d::Size checkSize)
{
    kWorldPos = pSrite->convertToNodeSpace(kWorldPos);      //转换成节点坐标
    cocos2d::Rect elementRect;
    elementRect = cocos2d::Rect(
            0,0,
            checkSize.width,
            checkSize.height
    );
    return elementRect.containsPoint(kWorldPos);
}

checkSize 为指定的检测范围。具体代码是下面这个样子:

case ui::Widget::TouchEventType::MOVED: {
                    Vec2 a = pCard->getTouchBeganPosition();
                    Vec2 b = pCard->getTouchMovePosition();
                    if (abs(b.x - a.x) > 30) {      //出牌判定
                        m_bMove = true;
                    }
                    //取消状态
                    ui::Layout *pHandCardNode = dynamic_cast<ui::Layout *>(UIHelper::seekNodeByName(m_pParseNode, "HandCard_0"));
                    const auto &arrayRootChildren = pHandCardNode->getChildren();
                    for (auto &subWidget : arrayRootChildren) {
                        subWidget->setName("");
                        UIHelper::seekNodeByName(subWidget, "ColorLayout")->setVisible(false);
                    }

                    float lx = a.x >= b.x ? a.x : b.x;
                    float sx = a.x >= b.x ? b.x : a.x;
                    float by = b.y;
                    while (lx - sx > 10.0f) {    //距离10检测一次
                        lx -= 10.0f;
                        ui::Layout *pTempHandCardNode = dynamic_cast<ui::Layout *>(UIHelper::seekNodeByName(m_pParseNode, "HandCard_0"));
                        const auto &arrayTempRootChildren = pTempHandCardNode->getChildren();
                        ssize_t lsize = arrayTempRootChildren.size();
                        for (int i = 0; i < lsize; i++) {
                            ui::Layout *child = dynamic_cast<ui::Layout *>(arrayTempRootChildren.at(i));
                            Size _cardSize = child->getContentSize();
                            if (i == lsize - 1) {     //最后一张
                                if (UIHelper::IsSpriteInTouch(Vec2(lx, by), child)) {
                                    child->setName("_InTouch_Card_");
                                    UIHelper::seekNodeByName(child, "ColorLayout")->setVisible(true);
                                    break; //成功检测后退出
                                }
                            } else {
                                if (UIHelper::IsSpriteInTouch(Vec2(lx, by), child, Size(m_fBigCardW, _cardSize.height))) {
                                    child->setName("_InTouch_Card_");
                                    UIHelper::seekNodeByName(child, "ColorLayout")->setVisible(true);
                                    break;  //成功检测后退出
                                }
                            }
                        }
                    }
                    break;
                }

通过->setName(“_InTouch_Card_”);给扑克打标记,方便触摸结束时候处理;ColorLayout是一层颜色遮罩层,用来给选中的扑克添加底色;
后续的处理代码大致如下:筛选出最终的扑克牌列表,更改Y轴坐标。

case ui::Widget::TouchEventType::CANCELED:
                case ui::Widget::TouchEventType::ENDED: {
                    if (m_bMove) {
                        ui::Layout *pHandCardNode = dynamic_cast<ui::Layout *>(UIHelper::seekNodeByName(m_pParseNode, "HandCard_0"));
                        const auto &arrayRootChildren = pHandCardNode->getChildren();
                        BYTE cbRemoveCard[MAX_COUNT];   //移除列表
                        ZeroMemory(cbRemoveCard, sizeof(cbRemoveCard));
                        BYTE cbRemoveCardCount = 0;     //移除总数
                        BYTE cbAddCard[MAX_COUNT];      //添加列表
                        ZeroMemory(cbAddCard, sizeof(cbAddCard));
                        BYTE cbAddCardCount = 0;        //添加总数
                        for (auto &subWidget : arrayRootChildren) {
                            Node *child = dynamic_cast<Node *>(subWidget);
                            if (child->getName() == "_InTouch_Card_") {   //触摸的牌
                                bool isExist = false;
                                BYTE cbTag = (BYTE) child->getTag();
                                for (int i = 0; i < m_cbOutCardCount; i++) { isExist = (m_cbOutCardData[i] == cbTag); if (isExist) break; //找到了就退出 } if (isExist) { cbRemoveCard[cbRemoveCardCount++] = cbTag; } else { cbAddCard[cbAddCardCount++] = cbTag; } child->setName("");
                            }
                        }
                        if (cbRemoveCardCount > 0) {
                            m_pGameClient->m_pGameLogic->RemoveCardList(cbRemoveCard, cbRemoveCardCount, m_cbOutCardData, m_cbOutCardCount);
                            m_cbOutCardCount -= cbRemoveCardCount;  //减少数量
                        }
                        CopyMemory(m_cbOutCardData + m_cbOutCardCount, cbAddCard, cbAddCardCount);
                        m_cbOutCardCount += cbAddCardCount;
                        //牌处理
                        CCLOG("选中牌的数量:%d,新增牌的数量:%d,移除牌的数量", m_cbOutCardCount, cbAddCardCount, cbRemoveCardCount);
                        ShowSearchCard(m_cbOutCardData, m_cbOutCardCount);
                        return;
                    }