├── .env
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── app
├── Annotation
│ ├── Mapping
│ │ └── AlphaDash.php
│ └── Parser
│ │ └── AlphaDashParser.php
├── Application.php
├── Aspect
│ └── AnnotationAspect.php
├── AutoLoader.php
├── Common
│ └── TcpReceiveListener.php
├── Console
│ ├── .gitkeep
│ └── Command
│ │ └── .gitkeep
├── Exception
│ ├── ApiException.php
│ └── Handler
│ │ ├── ApiExceptionHandler.php
│ │ ├── HttpExceptionHandler.php
│ │ ├── RpcExceptionHandler.php
│ │ ├── WsHandshakeExceptionHandler.php
│ │ └── WsMessageExceptionHandler.php
├── Game
│ ├── Conf
│ │ ├── MainCmd.php
│ │ ├── Route.php
│ │ └── SubCmd.php
│ ├── Core
│ │ ├── AStrategy.php
│ │ ├── DdzPoker.php
│ │ ├── Dispatch.php
│ │ ├── JokerPoker.php
│ │ ├── Log.php
│ │ └── Packet.php
│ └── Logic
│ │ ├── ChatMsg.php
│ │ ├── GameCall.php
│ │ ├── GameOutCard.php
│ │ ├── GameStart.php
│ │ └── HeartAsk.php
├── Helper
│ ├── .gitkeep
│ └── Functions.php
├── Http
│ ├── Controller
│ │ └── GameController.php
│ └── Middleware
│ │ └── SomeMiddleware.php
├── Listener
│ ├── .gitkeep
│ └── BeforeStartListener.php
├── Migration
│ └── .gitkeep
├── Model
│ ├── Dao
│ │ └── .gitkeep
│ ├── Data
│ │ └── .gitkeep
│ ├── Entity
│ │ └── .gitkeep
│ └── Logic
│ │ └── .gitkeep
├── Process
│ └── .gitkeep
├── Rpc
│ ├── Lib
│ │ ├── DbproxyInterface.php
│ │ └── UserInterface.php
│ ├── Middleware
│ │ └── ServiceMiddleware.php
│ └── Service
│ │ ├── DbproxyService.php
│ │ ├── UserService.php
│ │ ├── UserServiceV2.php
│ │ └── big.data
├── Task
│ ├── Listener
│ │ └── FinishListener.php
│ └── Task
│ │ ├── GameSyncTask.php
│ │ ├── SyncTask.php
│ │ └── TestTask.php
├── Validator
│ └── .gitkeep
├── WebSocket
│ ├── GameModule.php
│ └── VedioModule.php
└── bean.php
├── bin
├── bootstrap.php
└── swoft
├── composer.json
├── config
├── base.php
└── game.php
├── images
├── ai.jpg
├── demo1.png
└── demo2.png
├── public
├── .keep
└── client
│ ├── Const.js
│ ├── Init.js
│ ├── Packet.js
│ ├── Req.js
│ ├── Resp.js
│ ├── index.html
│ └── msgpack.js
├── resource
└── views
│ ├── game
│ ├── game.php
│ ├── login.php
│ └── test.php
│ └── vedio
│ ├── camera.php
│ └── show.php
└── test
├── ai.php
├── tcp_client.php
├── test_redis.php
└── test_rpc.php
/.env:
--------------------------------------------------------------------------------
1 | APP_DEBUG = 0
2 | SWOFT_DEBUG = 0
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .git/
3 | runtime/
4 | vendor/
5 | *.lock
6 | public/devtool/
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # @description php image base on the debian 9.x
2 | #
3 | # Some Information
4 | # ------------------------------------------------------------------------------------
5 | # @link https://hub.docker.com/_/debian/ alpine image
6 | # @link https://hub.docker.com/_/php/ php image
7 | # @link https://github.com/docker-library/php php dockerfiles
8 | # @see https://github.com/docker-library/php/tree/master/7.2/stretch/cli/Dockerfile
9 | # ------------------------------------------------------------------------------------
10 | # @build-example docker build . -f Dockerfile -t swoft/swoft
11 | #
12 | FROM php:7.2
13 |
14 | LABEL maintainer="jxy918 <251413215@qq.com>" version="2.3"
15 |
16 | # --build-arg timezone=Asia/Shanghai
17 | ARG timezone
18 | # app env: prod pre test dev
19 | ARG app_env=prod
20 | # default use www-data user
21 | ARG work_user=www-data
22 |
23 | ENV APP_ENV=${app_env:-"prod"} \
24 | TIMEZONE=${timezone:-"Asia/Shanghai"} \
25 | PHPREDIS_VERSION=4.3.0 \
26 | SWOOLE_VERSION=4.4.17 \
27 | COMPOSER_ALLOW_SUPERUSER=1
28 |
29 | # Libs -y --no-install-recommends
30 | RUN apt-get update \
31 | && apt-get install -y \
32 | curl wget git zip unzip less vim openssl \
33 | libz-dev \
34 | libssl-dev \
35 | libnghttp2-dev \
36 | libpcre3-dev \
37 | libjpeg-dev \
38 | libpng-dev \
39 | libfreetype6-dev \
40 | # Install composer
41 | && curl -sS https://getcomposer.org/installer | php \
42 | && mv composer.phar /usr/local/bin/composer \
43 | && composer self-update --clean-backups \
44 | # Install PHP extensions
45 | && docker-php-ext-install \
46 | bcmath gd pdo_mysql mbstring sockets zip sysvmsg sysvsem sysvshm \
47 | # Install redis extension
48 | && wget http://pecl.php.net/get/redis-${PHPREDIS_VERSION}.tgz -O /tmp/redis.tar.tgz \
49 | && pecl install /tmp/redis.tar.tgz \
50 | && rm -rf /tmp/redis.tar.tgz \
51 | && docker-php-ext-enable redis \
52 | # Install swoole extension
53 | && wget https://github.com/swoole/swoole-src/archive/v${SWOOLE_VERSION}.tar.gz -O swoole.tar.gz \
54 | && mkdir -p swoole \
55 | && tar -xf swoole.tar.gz -C swoole --strip-components=1 \
56 | && rm swoole.tar.gz \
57 | && ( \
58 | cd swoole \
59 | && phpize \
60 | && ./configure --enable-mysqlnd --enable-sockets --enable-openssl --enable-http2 \
61 | && make -j$(nproc) \
62 | && make install \
63 | ) \
64 | && rm -r swoole \
65 | && docker-php-ext-enable swoole \
66 |
67 | #Install msgpack extension
68 | && wget http://pecl.php.net/get/msgpack-2.0.3.tgz -O msgpack-2.0.3.tgz \
69 | && mkdir -p msgpack \
70 | && tar -xf msgpack-2.0.3.tgz -C msgpack --strip-components=1 \
71 | && ( \
72 | cd msgpack \
73 | && phpize \
74 | && ./configure \
75 | && make \
76 | && make install \
77 | ) \
78 | && rm -r msgpack \
79 | && docker-php-ext-enable msgpack \
80 |
81 | # Clear dev deps
82 | && apt-get clean \
83 | && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
84 | # Timezone
85 | && cp /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
86 | && echo "${TIMEZONE}" > /etc/timezone \
87 | && echo "[Date]\ndate.timezone=${TIMEZONE}" > /usr/local/etc/php/conf.d/timezone.ini
88 |
89 | # Install composer deps
90 | #ADD . /var/www/swoft
91 | #RUN cd /var/www/swoft \
92 | # && composer install \
93 | # && composer clearcache
94 | #
95 | WORKDIR /var/www/swoft
96 | EXPOSE 18306 18307 18308
97 |
98 | # ENTRYPOINT ["php", "/var/www/swoft/bin/swoft", "http:start"]
99 | # CMD ["php", "/var/www/swoft/bin/swoft", "http:start"]
100 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # swoft-ddz(斗地主)
2 |
3 | * 基于swoft2.0框架开发游戏斗地主
4 |
5 | * 使用swoft2.0框架基本实现斗地主游戏逻辑, 可以简单玩斗地主
6 | * 客户端采用纯原生js编写, 实现简单的测试客户端, 没有任何动画效果
7 | * 实现斗地主AI机器人简单逻辑,现在只能是过牌,比较弱智,可以简单陪打,机器人逻辑会慢慢完善
8 |
9 | * 想关注更多游戏开发可以关注hyperf-ddz斗地主:**[hyperf-ddz](https://github.com/jxy918/hyperf-ddz)**
10 |
11 | ### 一,概述
12 |
13 | * 使用swoft2.0开发实现斗地主游戏服务器逻辑, 并采用原生js实现简单的客户端打牌逻辑,基本可以做到简单玩斗地主游戏了, 并实现机器人AI逻辑编写, 全方面实现斗地主游戏逻辑。
14 | * 整个斗地主逻辑实现是在redis里存储的, 并没有接入数据库, 数据库接入, 可以在游戏登录或者斗地主打牌完成, 接入。
15 |
16 | ### 二,示例图
17 | 1, 登录, 简单实现登录, 请随便输入英文或这数字账号, 现在直接是采用账号当uid使用存入redis的,如果接入数据库, 请自行通过账号替换成uid,登录如下图:
18 | 
19 |
20 | 2, 打牌逻辑, 根据按钮来操作打牌逻辑, 消息框里会提示打牌逻辑过程,打牌逻辑如下图:
21 | 
22 |
23 |
24 | ### 三,特性
25 |
26 | * 实现前后端二进制封包解包,采用的是msgpack扩展,msgpack对数据进行了压缩,并实现粘包处理
27 | * 数据采用固定包头,包头4个字节存包体长度,包体前2个字节分别为cmd(主命令字),scmd(子命令字),后面为包体内容
28 | * 采用策略模式解耦游戏中的每个协议逻辑
29 | * 实现定义游戏开发cmd(主命令字)和scmd(子命令字)定义,cmd和scmd主要用来路由到游戏协议逻辑处理
30 | * 代码里有个DdzPoker类是一个款斗地主算法类.
31 | * 多地主逻辑是单独目录是实现的, 可以方便迁移框架。
32 |
33 | ### 四,环境依赖
34 |
35 | >依赖swoft环境,请安装php扩展msgpack
36 |
37 | * php vesion > 7.0
38 | * swoole version > 4.4.1
39 | * msgpack
40 | * swoft vesion > 2.0
41 |
42 |
43 | ### 五,开始使用
44 | * 1,安装
45 |
46 | ```
47 | composer install
48 |
49 | ```
50 |
51 | * 2,目录说明(swoft目录不具体说明):
52 |
53 | ```
54 | ./app/Http/Controller/GameController.php 游戏http控制器逻辑
55 | ./app/Game 是这个整体游戏服务器逻辑
56 | ./app/Game/Conf 逻辑配置目录, 比如:命令字, 子名字, 路由转发
57 | ./app/Game/Core 游戏路由转发,算法,解包核心类
58 | ./app/Game/Logic 游戏路由转发逻辑协议包处理目录
59 | ./public/client 客户端view的资源文件
60 | ./resources/views/game 斗地主客户端
61 | ./app/Task/Task/GameSyncTask.php 用户进入房间异步匹配处理逻辑
62 | ./app/Common/TcpReceiveListener.php 重新覆盖tcp服务器路由到游戏处理逻辑, 此文件开tcp协议, 让斗地主逻辑支持tcp协议
63 |
64 | ```
65 |
66 | * 3,进入根目录目录,启动服务器(swoft启动websocket启动法) :
67 |
68 | 游戏服务器命令操作:
69 |
70 | ```
71 | // 启动服务,根据
72 | php bin/swoft ws:start
73 |
74 | // 守护进程启动,覆盖
75 | php bin/swoft ws:start -d
76 |
77 | // 重启
78 | php bin/swoft ws:restart
79 |
80 | // 重新加载
81 | php bin/swoft ws:reload
82 |
83 | // 关闭服务
84 | php bin/swoft ws:stop
85 |
86 | ```
87 |
88 | 机器人AI执行命令操作, 进入根目录下test目录下
89 |
90 | ```
91 | //xxx表示用户账号
92 | php ai.php xxx
93 |
94 | ```
95 | > xxx 表示用户账号, 账号最好用英文加数字, 账号唯一就可以
96 |
97 | > ai功能需要swoole开启短名称功能, 去php.ini添加:swoole.use_shortname=on
98 |
99 | 
100 |
101 | * 4,访问url:
102 |
103 | ```
104 | //斗地主客户端入口
105 | http://[ip]:[port]/game
106 |
107 | ```
108 |
109 |
110 | ### 六,联系方式
111 |
112 | * qq:251413215,加qq请输入验证消息:swoft-ddz
113 | qq群:100754069
114 |
115 | ### 七,备注
116 |
117 | * 可以使用根目录增加docker运行环境(Dockerfile), 可以直接执行下面的命令,创建镜像php_swoole, 环境增加php-protobuf,php-msgpack支持。
118 |
119 | ```
120 | docker build -t php_swoole .
121 |
122 | ```
123 | * 注意如果程序不能自动加载,请去除环境中opcache扩展。
124 | * 服务器增加支持TCP服务器,服务器启动就会监控TCP游戏服务器, 可以通过/test/tcp_client.php测试。
125 |
126 | ```
127 | php ./test/tcp_client
128 |
129 | ```
130 |
131 | * 请使用根目录下的Dockerfile可以直接跑, 如果使用swoft的Dockerfile需要自行安装msgpack扩展,
132 |
133 | ### 八,增加录像直播功能, 此功能只是测试, 效果比较差
134 |
135 | * 1, 视频录制url,如果电脑没有摄像头, 请用手机测试录制视频:
136 |
137 | ````
138 | http://[ip]:[port]/camera
139 | ````
140 |
141 | * 2,浏览器上播放视频url如下:
142 |
143 | ```
144 | http://[ip]:[port]/show
145 | ```
146 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/app/Annotation/Mapping/AlphaDash.php:
--------------------------------------------------------------------------------
1 | message = $values['value'];
39 | }
40 | if (isset($values['message'])) {
41 | $this->message = $values['message'];
42 | }
43 | if (isset($values['name'])) {
44 | $this->name = $values['name'];
45 | }
46 | }
47 |
48 | /**
49 | * @return string
50 | */
51 | public function getMessage(): string
52 | {
53 | return $this->message;
54 | }
55 |
56 | /**
57 | * @return string
58 | */
59 | public function getName(): string
60 | {
61 | return $this->name;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/Annotation/Parser/AlphaDashParser.php:
--------------------------------------------------------------------------------
1 | className, $this->propertyName, $annotationObject);
33 | return [];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/Application.php:
--------------------------------------------------------------------------------
1 | __DIR__,
23 | ];
24 | }
25 |
26 | /**
27 | * @return array
28 | */
29 | public function metadata(): array
30 | {
31 | return [];
32 | }
33 | }
--------------------------------------------------------------------------------
/app/Common/TcpReceiveListener.php:
--------------------------------------------------------------------------------
1 | log("Receive: conn#{$fd} received client request, begin init context", [], 'debug');
50 | $sid = (string)$fd;
51 | $ctx = TcpReceiveContext::new($fd, $request, $response);
52 |
53 | // Storage context
54 | Context::set($ctx);
55 | Session::bindCo($sid);
56 |
57 | try {
58 | Log::show(" Receive: client #{$fd} send success Mete: \n{");
59 | if(!empty($data)) {
60 | $data = Packet::packDecode($data);
61 | $back = '';
62 | if (isset($data['code']) && $data['code'] == 0 && isset($data['msg']) && $data['msg'] == 'OK') {
63 | Log::show('Recv <<< cmd=' . $data['cmd'] . ' scmd=' . $data['scmd'] . ' len=' . $data['len'] . ' data=' . json_encode($data['data']));
64 | //转发请求,代理模式处理,websocket路由到相关逻辑
65 | $data['serv'] = $server;
66 | $back = $this->dispatch($data);
67 | Log::show('Tcp Strategy <<< data=' . $back);
68 | } else {
69 | Log::show($data['msg']);
70 | }
71 | if (!empty($back)) {
72 | $server->send($fd, $back);
73 | }
74 | }
75 | Log::split('}');
76 | } catch (Throwable $e) {
77 | server()->log("Receive: conn#{$fd} error: " . $e->getMessage(), [], 'error');
78 | Swoft::trigger(TcpServerEvent::RECEIVE_ERROR, $e, $fd);
79 |
80 | /** @var TcpErrorDispatcher $errDispatcher */
81 | $errDispatcher = Swoft::getSingleton(TcpErrorDispatcher::class);
82 |
83 | // Dispatching error handle
84 | $response = $errDispatcher->receiveError($e, $response);
85 | $response->send($server);
86 | } finally {
87 | // Defer
88 | Swoft::trigger(SwoftEvent::COROUTINE_DEFER);
89 |
90 | // Destroy
91 | Swoft::trigger(SwoftEvent::COROUTINE_COMPLETE);
92 |
93 | // Unbind cid => sid(fd)
94 | Session::unbindCo();
95 | }
96 | }
97 |
98 | /**
99 | * 根据路由策略处理逻辑,并返回数据
100 | * @param $data
101 | * @return string
102 | */
103 | public function dispatch($data) {
104 | $obj = new Dispatch($data);
105 | $back = "
404 Not Found swoft \n";
106 | if(!empty($obj->getStrategy())) {
107 | $back = $obj->exec();
108 | }
109 | return $back;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/Console/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxy918/swoft-ddz/07c42b2437691fa502e6891384f9bd4ce7d79801/app/Console/.gitkeep
--------------------------------------------------------------------------------
/app/Console/Command/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxy918/swoft-ddz/07c42b2437691fa502e6891384f9bd4ce7d79801/app/Console/Command/.gitkeep
--------------------------------------------------------------------------------
/app/Exception/ApiException.php:
--------------------------------------------------------------------------------
1 | $except->getCode(),
32 | 'error' => sprintf('(%s) %s', get_class($except), $except->getMessage()),
33 | 'file' => sprintf('At %s line %d', $except->getFile(), $except->getLine()),
34 | 'trace' => $except->getTraceAsString(),
35 | ];
36 | return $response->withData($data);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/Exception/Handler/HttpExceptionHandler.php:
--------------------------------------------------------------------------------
1 | getMessage());
35 |
36 | // Debug is false
37 | if (!APP_DEBUG) {
38 | return $response->withStatus(500)->withContent(
39 | sprintf(' %s At %s line %d', $e->getMessage(), $e->getFile(), $e->getLine())
40 | );
41 | }
42 |
43 | $data = [
44 | 'code' => $e->getCode(),
45 | 'error' => sprintf('(%s) %s', get_class($e), $e->getMessage()),
46 | 'file' => sprintf('At %s line %d', $e->getFile(), $e->getLine()),
47 | 'trace' => $e->getTraceAsString(),
48 | ];
49 |
50 | // Debug is true
51 | return $response->withData($data);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/Exception/Handler/RpcExceptionHandler.php:
--------------------------------------------------------------------------------
1 | getMessage(), $e->getFile(), $e->getLine());
39 | $error = Error::new($e->getCode(), $message, null);
40 | } else {
41 | $error = Error::new($e->getCode(), $e->getMessage(), null);
42 | }
43 |
44 | Debug::log('Rpc server error(%s)', $e->getMessage());
45 |
46 | $response->setError($error);
47 |
48 | // Debug is true
49 | return $response;
50 | }
51 | }
--------------------------------------------------------------------------------
/app/Exception/Handler/WsHandshakeExceptionHandler.php:
--------------------------------------------------------------------------------
1 | withStatus(500)->withContent(sprintf(
35 | '%s At %s line %d', $e->getMessage(), $e->getFile(), $e->getLine()
36 | ));
37 | }
38 |
39 | $data = [
40 | 'code' => $e->getCode(),
41 | 'error' => sprintf('(%s) %s', get_class($e), $e->getMessage()),
42 | 'file' => sprintf('At %s line %d', $e->getFile(), $e->getLine()),
43 | 'trace' => $e->getTraceAsString(),
44 | ];
45 |
46 | // Debug is true
47 | return $response->withData($data);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/Exception/Handler/WsMessageExceptionHandler.php:
--------------------------------------------------------------------------------
1 | getMessage(), $e->getFile(), $e->getLine());
34 |
35 | Log::error('Ws server error(%s)', $message);
36 |
37 | // Debug is false
38 | if (!APP_DEBUG) {
39 | server()->push($frame->fd, $e->getMessage());
40 | return;
41 | }
42 |
43 | server()->push($frame->fd, $message);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/Game/Conf/MainCmd.php:
--------------------------------------------------------------------------------
1 | array(
20 | SubCmd::HEART_ASK_REQ =>'HeartAsk',
21 | ),
22 | //游戏请求
23 | MainCmd::CMD_GAME => array(
24 | SubCmd::SUB_GAME_START_REQ =>'GameStart',
25 | SubCmd::SUB_GAME_CALL_REQ =>'GameCall',
26 | SubCmd::SUB_GAME_DOUBLE_REQ =>'GameDouble',
27 | SubCmd::SUB_GAME_OUT_CARD_REQ =>'GameOutCard',
28 | SubCmd::CHAT_MSG_REQ =>'ChatMsg',
29 | ),
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/app/Game/Conf/SubCmd.php:
--------------------------------------------------------------------------------
1 | CGameStart
22 | const SUB_GAME_START_RESP = 2; //游戏场景---> CGameScence
23 | const SUB_USER_INFO_RESP = 3; //用户信息 ------> CUserInfo
24 | const SUB_GAME_SEND_CARD_RESP = 4; //发牌 ------> CGameSendCard
25 | const SUB_GAME_CALL_TIPS_RESP = 5; //叫地主提示(广播) --> CGameCall
26 | const SUB_GAME_CALL_REQ = 6; //叫地主请求 --> CGameCallReq
27 | const SUB_GAME_CALL_RESP = 7; //叫地主请求返回--CGameCallResp
28 | const SUB_GAME_DOUBLE_TIPS_RESP = 8; //加倍提示(广播) --> CGameDouble
29 | const SUB_GAME_DOUBLE_REQ = 9; //加倍请求--> CGameDoubleReq
30 | const SUB_GAME_DOUBLE_RESP = 10; //加倍请求返回----> CGameDoubleResp
31 | const SUB_GAME_CATCH_BASECARD_RESP = 11; //摸底牌 ---> CGameCatchBaseCard
32 | const SUB_GAME_OUT_CARD = 12; //出牌提示 --> CGameOutCard
33 | const SUB_GAME_OUT_CARD_REQ = 13; //出牌请求 --> CGameOutCardReq
34 | const SUB_GAME_OUT_CARD_RESP = 14; //出牌返回 --> CGameOutCardResp
35 | const CHAT_MSG_REQ = 213; //聊天消息请求,客户端使用
36 | const CHAT_MSG_RESP = 214; //聊天消息响应,服务端使用
37 | }
--------------------------------------------------------------------------------
/app/Game/Core/AStrategy.php:
--------------------------------------------------------------------------------
1 | _params = $params;
36 | $this->obj_ddz = new DdzPoker();
37 | }
38 |
39 | /**
40 | * 执行方法,每条游戏协议,实现这个方法就行
41 | */
42 | abstract public function exec();
43 |
44 | /**
45 | * 服务器广播消息, 此方法是给所有的连接客户端, 广播消息
46 | * @param $serv
47 | * @param $data
48 | */
49 | protected function Broadcast($serv, $data)
50 | {
51 | foreach($serv->connections as $fd) {
52 | $serv->push($fd, $data, WEBSOCKET_OPCODE_BINARY);
53 | }
54 | }
55 |
56 | /**
57 | * 当connetions属性无效时可以使用此方法,服务器广播消息, 此方法是给所有的连接客户端, 广播消息,通过方法getClientList广播
58 | * @param $serv
59 | * @param $data
60 | */
61 | protected function BroadCast2($serv, $data)
62 | {
63 | $start_fd = 0;
64 | while(true) {
65 | $conn_list = $serv->getClientList($start_fd, 10);
66 | if ($conn_list===false or count($conn_list) === 0) {
67 | Log::show("BroadCast finish");
68 | break;
69 | }
70 | $start_fd = end($conn_list);
71 | foreach($conn_list as $fd) {
72 | //获取客户端信息
73 | $client_info = $serv->getClientInfo($fd);
74 | if(isset($client_info['websocket_status']) && $client_info['websocket_status'] == 3) {
75 | $serv->push($fd, $data, WEBSOCKET_OPCODE_BINARY);
76 | }
77 | }
78 | }
79 | }
80 |
81 | /**
82 | * 对多用发送信息
83 | * @param $serv
84 | * @param $users
85 | * @param $data
86 | */
87 | protected function pushToUsers($serv, $users, $data)
88 | {
89 | foreach($users as $fd) {
90 | //获取客户端信息
91 | $client_info = $serv->getClientInfo($fd);
92 | $client[$fd] = $client_info;
93 | if(isset($client_info['websocket_status']) && $client_info['websocket_status'] == 3) {
94 | $serv->push($fd, $data, WEBSOCKET_OPCODE_BINARY);
95 | }
96 | }
97 | }
98 |
99 | /**
100 | * 获取房间信息
101 | * @param $account
102 | * @return array
103 | */
104 | protected function getRoomData($account)
105 | {
106 | $user_room_data = array();
107 | //获取用户房间号
108 | $room_no = $this->getRoomNo($account);
109 | //房间信息
110 | $game_key = $this->getGameConf('user_room_data');
111 | if($game_key) {
112 | $user_room_key = sprintf($game_key, $room_no);
113 | $user_room_data = Redis::hGetAll($user_room_key);
114 | }
115 | return $user_room_data;
116 | }
117 |
118 | /**
119 | * 获取房间信息通过key
120 | * @param $account
121 | * @param $key
122 | * @return mixed
123 | */
124 | protected function getRoomDataByKey($account, $key)
125 | {
126 | $data = array();
127 | $no = $this->getRoomNo($account);
128 | $game_key = $this->getGameConf('user_room_data');
129 | if($no && $game_key) {
130 | $user_room_key = sprintf($game_key, $no);
131 | $user_room_data = Redis::hGet($user_room_key, $key);
132 | $data = json_decode($user_room_data, true);
133 | if (is_null($data)) {
134 | $data = $user_room_data;
135 | }
136 | }
137 | return $data;
138 | }
139 |
140 | /**
141 | * 获取用户房间号
142 | * @param $account
143 | * @return mixed
144 | */
145 | protected function getRoomNo($account)
146 | {
147 | $game_key = $this->getGameConf('user_room');
148 | //获取用户房间号
149 | $room_key = sprintf($game_key, $account);
150 | $room_no = Redis::get($room_key);
151 | return $room_no ? $room_no : 0;
152 | }
153 |
154 | /**
155 | * 获取房间信息通过key
156 | * @param $account
157 | * @return mixed
158 | */
159 | protected function getRoomUserInfoDataByKey($account)
160 | {
161 | $user_data = array();
162 | $no = $this->getRoomNo($account);
163 | $game_key = $this->getGameConf('user_room_data');
164 | if($no && $game_key) {
165 | //房间信息
166 | $user_room_key = sprintf($game_key, $no);
167 | $user_data = Redis::hGet($user_room_key, $account);
168 | $user_data = json_decode($user_data, true);
169 | }
170 | return $user_data;
171 | }
172 |
173 | /**
174 | * 设置房间用户玩牌信息
175 | * @param $account
176 | * @param $key
177 | * @param $value
178 | */
179 | protected function setRoomData($account, $key, $value)
180 | {
181 | $no = $this->getRoomNo($account);
182 | $game_key = $this->getGameConf('user_room_data');
183 | if($no && $game_key) {
184 | $user_room_key = sprintf($game_key, $no);
185 | Redis::hSet($user_room_key, $key, $value);
186 | }
187 | }
188 |
189 | /**
190 | * 批量设置房间信息
191 | * @param $account
192 | * @param $params
193 | */
194 | protected function muitSetRoomData($account, $params)
195 | {
196 | $no = $this->getRoomNo($account);
197 | $game_key = $this->getGameConf('user_room_data');
198 | if($no && $game_key) {
199 | $user_room_key = sprintf($game_key, $no);
200 | Redis::hMSet($user_room_key, $params);
201 | }
202 | }
203 |
204 | /**
205 | * 设置房间信息
206 | * @param $room_user_data
207 | * @param $account
208 | * @param $key
209 | * @param $value
210 | */
211 | protected function setRoomUserInfoDataByKey($room_user_data, $account, $key, $value)
212 | {
213 | $no = $this->getRoomNo($account);
214 | $game_key = $this->getGameConf('user_room_data');
215 | if($no && $game_key) {
216 | $room_user_data[$key] = $value;
217 | $user_room_key = sprintf($game_key, $no);
218 | Redis::hSet($user_room_key, $account, json_encode($room_user_data));
219 | }
220 | }
221 |
222 | /**
223 | * 通过accounts获取fds
224 | * @param $account
225 | * @return array
226 | */
227 | protected function getRoomFds($account)
228 | {
229 | $accs = $this->getRoomDataByKey($account, 'uinfo');
230 | $game_key = $this->getGameConf('user_bind_key');
231 | $binds = $fds = array();
232 | if(!empty($accs) && $game_key) {
233 | foreach($accs as $v) {
234 | $binds[] = sprintf($game_key, $v);
235 | }
236 | $fds = Redis::mget($binds);
237 | }
238 | return $fds;
239 | }
240 |
241 | /**
242 | * 批量清除用户房间号
243 | * @param $users
244 | */
245 | protected function clearRoomNo($users) {
246 | $game_key = $this->getGameConf('user_room');
247 | if(is_array($users)) {
248 | foreach($users as $v) {
249 | $key = sprintf($game_key, $v);
250 | Redis::del($key);
251 |
252 | }
253 | }
254 | }
255 |
256 | /**
257 | * 把php数组存入redis的hash表中
258 | * @param $arr
259 | * @param $hash_key
260 | */
261 | protected function arrToHashInRedis($arr, $hash_key) {
262 | foreach($arr as $key=>$val) {
263 | Redis::hSet($hash_key,$key, json_encode($val));
264 | }
265 | }
266 |
267 | /**
268 | * 返回游戏配置
269 | * @param string $key
270 | * @return string
271 | */
272 | protected function getGameConf($key = '') {
273 | $conf = config('game');
274 | if(isset($conf[$key])) {
275 | return $conf[$key];
276 | } else {
277 | return '';
278 | }
279 | }
280 |
281 | /**
282 | * 设置游戏房间玩牌步骤信息, 方便后面录像回放
283 | * @param $account
284 | * @param $key
285 | * @param $value
286 | */
287 | protected function setRoomPlayCardStep($account, $key, $value)
288 | {
289 | $no = $this->getRoomNo($account);
290 | $game_key = $this->getGameConf('user_room_play');
291 | if($no && $game_key) {
292 | $play_key = sprintf($game_key, $no);;
293 | Redis::hSet($play_key, $key, $value);
294 | }
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/app/Game/Core/Dispatch.php:
--------------------------------------------------------------------------------
1 | _params = $params;
31 | $this->Strategy();
32 | }
33 |
34 | /**
35 | * 逻辑处理策略路由转发, 游戏逻辑策略转发, 根据主命令字和子命令字来转发
36 | */
37 | public function Strategy()
38 | {
39 | //获取路由策略
40 | $route = Route::$cmd_map;
41 | //获取策略类名
42 | $classname = isset($route[$this->_params['cmd']][$this->_params['scmd']]) ? $route[$this->_params['cmd']][$this->_params['scmd']] : '';
43 | //转发到对应目录处理逻辑
44 | $classname = 'App\Game\Logic\\'.$classname;
45 | if (class_exists($classname)) {
46 | $this->_strategy = new $classname($this->_params);
47 | Log::show("Class: $classname");
48 | } else {
49 | Log::show("Websockt Error: class is not support,cmd is {$this->_params['cmd']},scmd is {$this->_params['scmd']}");
50 | }
51 | }
52 |
53 | /**
54 | * 获取策略
55 | */
56 | public function getStrategy()
57 | {
58 | return $this->_strategy;
59 | }
60 |
61 | /**
62 | * 执行策略
63 | */
64 | public function exec()
65 | {
66 | return $this->_strategy->exec();
67 | }
68 | }
--------------------------------------------------------------------------------
/app/Game/Core/JokerPoker.php:
--------------------------------------------------------------------------------
1 | 'A',2=>'2',3=>'3',4=>'4',5=>'5',6=>'6',7=>'7',8=>'8',9=>'9',10=>'10',11=>'J',12=>'Q',13=>'K',
17 | 17=>'A',18=>'2',19=>'3',20=>'4',21=>'5',22=>'6',23=>'7',24=>'8',25=>'9',26=>'10',27=>'J',28=>'Q',29=>'K',
18 | 33=>'A',34=>'2',35=>'3',36=>'4',37=>'5',38=>'6',39=>'7',40=>'8',41=>'9',42=>'10',43=>'J',44=>'Q',45=>'K',
19 | 49=>'A',50=>'2',51=>'3',52=>'4',53=>'5',54=>'6',55=>'7',56=>'8',57=>'9',58=>'10',59=>'J',60=>'Q',61=>'K',
20 | 79=>'JOKER'
21 | );
22 |
23 | /**
24 | * 赖子的key值,和牌的key值对应
25 | * @var int
26 | */
27 | public static $laizi_value = 79;
28 |
29 | /**
30 | * 花色
31 | */
32 | public static $card_color = array(
33 | 0=>'方块',
34 | 1=>'黑桃',
35 | 2=>'红桃',
36 | 3=>'梅花'
37 | );
38 |
39 | /**
40 | * 牌型
41 | * @var array
42 | */
43 | public static $card_type = array(
44 | 0=>'非赢牌',
45 | 1=>'对K或者以上',
46 | 2=>'两对',
47 | 3=>'三条',
48 | 4=>'顺子',
49 | 5=>'同花',
50 | 6=>'葫芦',
51 | 7=>'四条',
52 | 8=>'同花顺',
53 | 9=>'五条',
54 | 10=>'带赖子皇家同花顺',
55 | 11=>'皇家同花顺'
56 | );
57 |
58 | /**
59 | * 牌型赔付的倍率
60 | * @var array
61 | */
62 | public static $card_rate = array(
63 | 0=>0,
64 | 1=>1,
65 | 2=>1,
66 | 3=>2,
67 | 4=>3,
68 | 5=>5,
69 | 6=>7,
70 | 7=>17,
71 | 8=>50,
72 | 9=>100,
73 | 10=>200,
74 | 11=>250
75 | );
76 |
77 | /**
78 | * 是否翻倍的概率配置:1表示不翻倍回收奖励,2.表示再来一次 3,表示奖励翻倍
79 | */
80 | public static $is_double_rate = array(
81 | 1=>5000,
82 | 2=>1000,
83 | 3=>4000
84 | );
85 |
86 | /**
87 | * 是否翻倍提示语
88 | */
89 | public static $is_double_msg = array(
90 | 1=>'不翻倍回收奖励',
91 | 2=>'再来一次,不回收奖励',
92 | 3=>'奖励翻倍'
93 | );
94 |
95 | /**
96 | * 是否有赖子牌, 如果有赖子牌,这个值就是true, 默认false
97 | */
98 | public static $is_laizi = false;
99 |
100 | /**
101 | * 是否为顺子,是true,否false
102 | */
103 | public static $is_shunzi = false;
104 |
105 | /**
106 | * 是否为最大顺子,是true,否false
107 | */
108 | public static $is_big_shunzi = false;
109 |
110 | /**
111 | * 是否为同花,是true,否false
112 | */
113 | public static $is_tonghua = false;
114 |
115 | /**
116 | * 随机获取5张牌,如果参数指定n张牌, 就补齐5-n张牌
117 | */
118 | public static function getFiveCard($arr = array())
119 | {
120 | $card = self::$card_value_list;
121 | $num = 5 - count($arr);
122 | if($num == 0) {
123 | $card_key = $arr;
124 | } else {
125 | //去除上面的牌, 防止重复出现
126 | foreach($arr as $v) {
127 | unset($card[$v]);
128 | }
129 | $card_key = array_rand($card, $num);
130 | if(!is_array($card_key)) {
131 | $card_key = array($card_key);
132 | }
133 | $card_key = array_merge($card_key,$arr);
134 | }
135 | return $card_key;
136 | }
137 | /**
138 | * 随机获取1张牌,不包括王
139 | */
140 | public static function getOneCard()
141 | {
142 | $card = self::$card_value_list;
143 | unset($card[79]);
144 | $card_key = array_rand($card, 1);
145 | if(!is_array($card_key)) {
146 | $card_key = array($card_key);
147 | }
148 | return $card_key;
149 | }
150 |
151 | /**
152 | * 去除赖子,并且排序
153 | */
154 | private static function exceptLaizi($arr)
155 | {
156 | $key = array_search(self::$laizi_value, $arr); //键值有可能0
157 | if($key !== false) {
158 | unset($arr[$key]);
159 | self::$is_laizi = true;
160 | } else {
161 | self::$is_laizi = false;
162 | }
163 | sort($arr);
164 | return $arr;
165 | }
166 |
167 | /**
168 | * 获取牌内容,根据牌的key,获取牌的内容
169 | */
170 | private static function getCard($arr)
171 | {
172 | $card = array();
173 | foreach($arr as $v) {
174 | $card[$v]= self::$card_value_list[$v];
175 | }
176 | return $card;
177 | }
178 |
179 | /**
180 | * 获取牌内容,并显示花色, 方便直观查看
181 | */
182 | public static function showCard($arr)
183 | {
184 | $show = array();
185 | $card = self::getCard($arr);
186 | foreach($card as $k=>$v) {
187 | if($k != self::$laizi_value) {
188 | $key = floor($k / 16);
189 | $show[] = self::$card_color[$key].'_'.$v;
190 | } else {
191 | $show[] = $v;
192 | }
193 |
194 | }
195 | return implode(',', $show);
196 | }
197 |
198 | /**
199 | * 不带赖子皇家同花顺
200 | */
201 | public static function isBigTongHuaShun()
202 | {
203 | return (self::$is_tonghua && self::$is_shunzi && self::$is_big_shunzi && !self::$is_laizi) ? true : false;
204 | }
205 |
206 | /**
207 | * 带来赖子皇家同花顺
208 | */
209 | public static function isBigTongHuaShunByLaizi()
210 | {
211 | return (self::$is_tonghua && self::$is_shunzi && self::$is_big_shunzi && self::$is_laizi) ? true : false;
212 | }
213 |
214 | /**
215 | * 是否为同花顺
216 | */
217 | public static function isTongHuaShun()
218 | {
219 | return (self::$is_tonghua && self::$is_shunzi) ? true : false;
220 | }
221 |
222 | /**
223 | * 是否为同花牌,判断同花的算法
224 | */
225 | public static function isTongHua($arr)
226 | {
227 | $sub = array();
228 | foreach($arr as $v) {
229 | $sub[] = floor($v / 16);
230 | }
231 | $u = array_unique($sub);
232 | if(count($u) == 1) {
233 | self::$is_tonghua = true;
234 | } else {
235 | self::$is_tonghua = false;
236 | }
237 | return self::$is_tonghua;
238 | }
239 |
240 | /**
241 | * 是否为顺子牌,判断顺子的算法
242 | */
243 | public static function isShunZi($arr)
244 | {
245 | $flag = 0;
246 | $card = self::getCard($arr);
247 | asort($card);
248 | $min = key($card) % 16;
249 | if($min >= 2 && $min <= 10) {
250 | //最小或者最大顺子,需要特殊处理
251 | /* if(($min == 2 || $min == 10) && array_search('A', $card) !== false) {
252 | $flag++;
253 | } */
254 | if( array_search('A', $card) !== false) {
255 | if($min == 2 ){
256 | $min = 1;
257 | }elseif ( $min == 10){
258 | $flag++;
259 | }
260 | }
261 | $cnt = count($arr);
262 | for($i = 1; $i < 5; $i++) {
263 | $next = $min + $i;
264 | if(in_array($next, $arr) || in_array(($next + 16), $arr) || in_array(($next + 32), $arr) || in_array(($next + 48), $arr)) {
265 | $flag++;
266 | }
267 | }
268 | }
269 | if($flag == $cnt - 1) {
270 | self::$is_shunzi = true;
271 | } else {
272 | self::$is_shunzi = false;
273 | }
274 | //是否为最大顺子,是true,否false
275 | if($min == 10) {
276 | self::$is_big_shunzi = true;
277 | } else {
278 | self::$is_big_shunzi = false;
279 | }
280 | return self::$is_shunzi;
281 | }
282 |
283 | /**
284 | * 取模值,算对子,两对,三张,四条,5条的算法
285 | */
286 | public static function _getModValue($arr)
287 | {
288 | $flag = $type = 0;
289 | $mod = array();
290 | foreach($arr as $k=>$v) {
291 | $mod[] = $v % 16;
292 | }
293 | $v = array_count_values($mod);
294 | $cnt = count($v);
295 | if(self::$is_laizi) {
296 | if(in_array(1, $v) && $cnt == 4) {
297 | //对子
298 | $card = self::getCard($arr);
299 | if(array_search('A', $card) !== false || array_search('K', $card) !== false){
300 | $type = 1; //对K或更大
301 | }
302 | } elseif(in_array(2, $v) && $cnt == 3) {
303 | $type = 3; //三张
304 | } elseif(in_array(2, $v) && $cnt == 2) {
305 | $type = 4; //葫芦
306 | } elseif(in_array(3, $v)) {
307 | $type = 5; //四条
308 | } elseif(in_array(4, $v)) {
309 | $type = 6; //五条
310 | }
311 | } else {
312 | if(in_array(2, $v) && $cnt == 4) {
313 | //对子
314 | $card = self::getCard($arr);
315 | $card_key = array_count_values($card);
316 | arsort($card_key);
317 | $kw = key($card_key);
318 | if($kw == 'A' || $kw == 'K'){
319 | $type = 1; //对K或更大
320 | }
321 | } elseif(in_array(2, $v) && $cnt == 3) {
322 | $type = 2; //两对
323 | } elseif(in_array(3, $v) && $cnt == 3) {
324 | $type = 3; //三张
325 | } elseif(in_array(3, $v) && $cnt == 2) {
326 | $type = 4; //葫芦
327 | } elseif(in_array(4, $v)) {
328 | $type = 5; //四条
329 | }
330 | }
331 | return $type;
332 | }
333 |
334 | /**
335 | * 五张
336 | */
337 | public static function isWuZhang($type)
338 | {
339 | return $type == 6 ? true : false;
340 | }
341 |
342 | /**
343 | * 四张
344 | */
345 | public static function isSiZhang($type)
346 | {
347 | return $type == 5 ? true : false;
348 | }
349 |
350 | /**
351 | * 葫芦
352 | */
353 | public static function isHulu($type)
354 | {
355 | return $type == 4 ? true : false;
356 | }
357 |
358 | /**
359 | * 三张
360 | */
361 | public static function isSanZhang($type)
362 | {
363 | return $type == 3 ? true : false;
364 | }
365 |
366 | /**
367 | * 两对
368 | */
369 | public static function isLiangDui($type)
370 | {
371 | return $type == 2 ? true : false;
372 | }
373 |
374 | /**
375 | * 大于对K或更大
376 | */
377 | public static function isDaYuQDui($type)
378 | {
379 | return $type == 1 ? true : false;
380 | }
381 |
382 | /**
383 | * 检查牌型,判断用户所翻的牌为那种牌型
384 | */
385 | public static function checkCardType($arr)
386 | {
387 | //去除赖子牌
388 | $arr_card = self::exceptLaizi($arr);
389 | $type = self::_getModValue($arr_card);
390 | if(self::isWuZhang($type)) {
391 | return 9; //五条
392 | } elseif(self::isSiZhang($type)) {
393 | return 7; //四条
394 | } elseif(self::isHulu($type)) {
395 | return 6; //葫芦,三张两对
396 | } elseif(self::isSanZhang($type)) {
397 | return 3; //三张
398 | } elseif(self::isLiangDui($type)) {
399 | return 2; //两对
400 | } else {
401 | $back = 0;
402 | if(self::isDaYuQDui($type)) {
403 | $back = 1; //对K或者大于
404 | }
405 | if(self::isShunZi($arr_card)) {
406 | $back = 4; //是否为顺子
407 | }
408 | if(self::isTongHua($arr_card)) {
409 | $back = 5; //是否为同花
410 | }
411 | if(self::isTongHuaShun()) {
412 | $back = 8; //是否为同花顺
413 | }
414 | if(self::isBigTongHuaShunByLaizi()) {
415 | $back = 10; //带赖子皇家同花顺
416 | }
417 | if(self::isBigTongHuaShun()) {
418 | $back = 11; //皇家同花顺
419 | }
420 | return $back;
421 | }
422 | }
423 |
424 | /**
425 | * 找出牌型里那些牌需要高亮显示
426 | */
427 | public static function highLight($arr, $type)
428 | {
429 | $card_key = array();
430 | $card = self::getCard($arr);
431 | $val = array_count_values($card);
432 | if($type > 3) {
433 | $card_key = $arr;
434 | } elseif($type == 3) {
435 | //三条
436 | arsort($val);
437 | $kw = key($val);
438 | $card_key = array();
439 | foreach($card as $k=>$v) {
440 | if($v == $kw || $k == self::$laizi_value) {
441 | $card_key[] = $k;
442 | }
443 | }
444 | } elseif($type == 2) {
445 | //两对
446 | $kw = $card_key = array();
447 | foreach($val as $k=>$v) {
448 | if($v == 2) {
449 | $kw[] = $k;
450 | }
451 | }
452 | foreach($card as $k=>$v) {
453 | if(in_array($v, $kw)) {
454 | $card_key[] = $k;
455 | }
456 | }
457 | } elseif($type == 1) {
458 | //对A后者对K
459 | foreach($card as $k=>$v) {
460 | if(in_array($v, array('A','K')) || $k == self::$laizi_value) {
461 | $card_val[$k] = $v;
462 | }
463 | }
464 | $t_val = array_count_values($card_val);
465 | arsort($t_val);
466 | $kw = key($t_val);
467 | if(!self::$is_laizi) {
468 | if(count($t_val) > 1) {
469 | foreach($card_val as $k=>$v) {
470 | if($kw != $v) {
471 | unset($card_val[$k]);
472 | }
473 | }
474 | }
475 | } else {
476 | //去除k
477 | if(count($t_val) > 2) {
478 | foreach($card_val as $k=>$v) {
479 | if($v == 'K') {
480 | unset($card_val[$k]);
481 | }
482 | }
483 | }
484 | }
485 | $card_key = array_keys($card_val);
486 | }
487 | return $card_key;
488 | }
489 |
490 | /**
491 | * 是否翻倍, 玩家翻倍处理
492 | */
493 | public static function getIsDoubleCard($m_card = 2, $pos = 2, $arr = array())
494 | {
495 | $list = self::$card_value_list;
496 | unset($list[self::$laizi_value]); //去除赖子大王
497 | $card_list = array_rand($list, 4);
498 | //概率运算
499 | if(!empty($arr)) {
500 | $rate = self::_getRate($arr);
501 | }else{
502 | $rate = self::_getRate(self::$is_double_rate);
503 | }
504 |
505 | $min = $m_card % 16;
506 | //拿到最大牌A和最小牌2的概率需要特殊处理一下
507 | if($min == 1 && $rate == 3) {
508 | //最大牌A出现, 对方肯定是平手或者输
509 | $rate = rand(1, 2);
510 | } elseif($min == 2 && $rate == 1) {
511 | //最小牌2,出现对方肯定是平手或者赢 // $rate = rand(2,3);
512 | $rate = rand(2,3);
513 | }
514 | //最小牌
515 | if($rate == 2) {
516 | //不翻倍,奖励不扣除
517 | $key = $min;
518 | } elseif($rate == 3) {
519 | //翻倍,奖励累加, 系统数, 发大牌
520 | if($min == 13) {
521 | $key = 1;
522 | } else {
523 | $key = rand($min+1, 13);
524 | }
525 | } else {
526 | //不翻倍,丢失全部奖励,系统赢发小牌
527 | if($min == 1) {
528 | $key = rand(2, 13);
529 | } else {
530 | $key = rand(2, $min-1);
531 | }
532 | }
533 | //根据key组牌
534 | $card_val = array($key, $key+16, $key+32, $key+48);
535 | //去除相同的值
536 | $card_val = array_diff($card_val, $card_list);
537 | $card_key = array_rand($card_val, 1);
538 | $card_list[$pos] = $card_val[$card_key];
539 | return array('result'=>$rate, 'msg'=>self::$is_double_msg[$rate], 'm_card'=>self::$card_value_list[$m_card],'pos_card'=>self::$card_value_list[$card_list[$pos]], 'pos'=>$pos, 'card'=>$card_list, 'show'=>self::showCard($card_list));
540 | }
541 |
542 | /**
543 | * 计算概率算法
544 | * @param array $prizes 奖品概率数组
545 | * 格式:array(奖品id => array( 'rate'=>概率),奖品id => array('rate'=>概率))
546 | * @return int
547 | */
548 | private static function _getRate($arr = array())
549 | {
550 | $key = 0;
551 | //首先生成一个1W内的数
552 | $rid = rand(1, 10000);
553 | //概率值(按设置累加)
554 | $rate = 0;
555 | foreach ($arr as $k => $v) {
556 | //根据设置的概率向上累加
557 | $rate += $v;
558 | //如果生成的概率数小于或等于此数据,表示当前道具ID即是,退出查找
559 | if($rid <= $rate) {
560 | $key = $k;
561 | break;
562 | }
563 | }
564 | return $key;
565 | }
566 |
567 | /**
568 | * 获取牌型结果
569 | */
570 | public static function getCardType($arr)
571 | {
572 | $type = self::checkCardType($arr);
573 | $highlight = self::highLight($arr, $type);
574 | return array('card'=>$arr, 'type'=>$type, 'typenote'=>self::$card_type[$type], 'rate'=>self::$card_rate[$type], 'highlight'=>$highlight);
575 | }
576 |
577 | /**
578 | * 设置翻倍的概率
579 | */
580 | public static function setRate($rate = array())
581 | {
582 | if(empty($rate)) {
583 | self::$is_double_rate = $rate;
584 | }
585 | }
586 | }
587 |
588 | /*
589 |
590 | header("Content-type: text/html; charset=utf-8");
591 |
592 | $act = isset($_REQUEST['act']) ? trim($_REQUEST['act']) : '';
593 | //类调用
594 | $obj = new JokerPoker();
595 |
596 | if($act == 'getcard') {
597 | //获取5张牌
598 | $key = $obj->getFiveCard();
599 | //$key = array(17,37,39,40,42);
600 | exit(json_encode($key));
601 | } elseif($act == 'turncard') {
602 | //翻牌
603 | $tmp = isset($_REQUEST['card']) ? trim($_REQUEST['card']) : '';
604 | if(!empty($tmp)) {
605 | $key = explode('|',$tmp);
606 | } else {
607 | $key = array();
608 | }
609 | $key = array_map('intval', $key);
610 | $card = $obj->getFiveCard($key);
611 | $res = $obj->getCardType($card);
612 | exit(json_encode($res));
613 | } elseif($act == 'isdouble') {
614 | //翻倍处理
615 | $card = isset($_REQUEST['card']) && !empty($_REQUEST['card']) ? intval($_REQUEST['card']) : 2;
616 | $pos = (isset($_REQUEST['pos']) && $_REQUEST['pos'] < 4) ? intval($_REQUEST['pos']) : 2;
617 | $res = $obj->getIsDoubleCard($card, $pos);
618 | exit(json_encode($res));
619 | }
620 |
621 | //测试牌型结果
622 | $tmp = isset($_REQUEST['test']) ? trim($_REQUEST['test']) : '';
623 | if(!empty($tmp)) {
624 | $key = explode('|',$tmp);
625 | } else {
626 | $key = array();
627 | }
628 |
629 | //类调用
630 | $obj = new JokerPoker();
631 | $key = $obj->getFiveCard();
632 | $key = array(13,18,24,27,43);
633 | $card = $obj->showCard($key);
634 |
635 | var_dump($key, $card, $obj->getCardType($key),$obj->getIsDoubleCard());
636 |
637 | */
638 |
--------------------------------------------------------------------------------
/app/Game/Core/Log.php:
--------------------------------------------------------------------------------
1 | 'INFO',
12 | 2 => 'DEBUG',
13 | 3=>'ERROR'
14 | );
15 |
16 | /**
17 | * 日志等级,1表示大于等于1的等级的日志,都会显示,依次类推
18 | * @var int
19 | */
20 | protected static $level = 1;
21 |
22 | /**
23 | * 显示日志
24 | * @param string $centent
25 | * @param int $level
26 | */
27 | public static function show($centent = '', $level = 1, $str = '')
28 | {
29 | if($level >= self::$level) {
30 | echo $str.date('Y/m/d H:i:s') . " [\033[0;36m" . self::$level_info[$level] . "\033[0m] " . $centent . "\n";
31 | }
32 | }
33 |
34 | /**
35 | * 显示日志
36 | * @param string $centent
37 | * @param int $level
38 | */
39 | public static function split($split = '', $level = 1)
40 | {
41 | if($level >= self::$level) {
42 | echo $split . "\n";
43 | }
44 | }
45 | }
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/app/Game/Core/Packet.php:
--------------------------------------------------------------------------------
1 | $code,
16 | "msg" => $msg,
17 | "data" => $data,
18 | );
19 | return $pack;
20 | }
21 |
22 | /**
23 | * 打包数据,固定包头,4个字节为包头(里面存了包体长度),包体前2个字节为
24 | */
25 | public static function packEncode($data, $cmd = 1, $scmd = 1, $format='msgpack', $type = "tcp")
26 | {
27 | if ($type == "tcp") {
28 | if($format == 'msgpack') {
29 | $sendStr = msgpack_pack($data);
30 | } else {
31 | $sendStr = $data;
32 | }
33 | $sendStr = pack('N', strlen($sendStr) + 2) . pack("C2", $cmd, $scmd). $sendStr;
34 | return $sendStr;
35 | } else {
36 | return self::packFormat("packet type wrong", 100006);
37 | }
38 | }
39 |
40 | /**
41 | * 解包数据
42 | */
43 | public static function packDecode($str, $format='msgpack')
44 | {
45 | $header = substr($str, 0, 4);
46 | if(strlen($header) != 4) {
47 | return self::packFormat("packet length invalid", 100007);
48 | } else {
49 | $len = unpack("Nlen", $header);
50 | $len = $len["len"];
51 | $result = substr($str, 6);
52 | if ($len != strlen($result) + 2) {
53 | //结果长度不对
54 | return self::packFormat("packet length invalid", 100007);
55 | }
56 |
57 | if($format == 'msgpack') {
58 | $result = msgpack_unpack($result);
59 | }
60 | if(empty($result)) {
61 | //结果长度不对
62 | return self::packFormat("packet data is empty", 100008);
63 | }
64 | $cmd = unpack("Ccmd/Cscmd", substr($str, 4, 6));
65 | $result = self::packFormat("OK", 0, $result);
66 | $result['cmd'] = $cmd['cmd'];
67 | $result['scmd'] = $cmd['scmd'];
68 | $result['len'] = $len + 4;
69 | return $result;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/Game/Logic/ChatMsg.php:
--------------------------------------------------------------------------------
1 | _params['data']);
18 | $data = Packet::packEncode($data, MainCmd::CMD_GAME, SubCmd::CHAT_MSG_RESP);
19 | return $data;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Game/Logic/GameCall.php:
--------------------------------------------------------------------------------
1 | _params['userinfo']['account'];
21 | $calltype = $this->_params['data']['type'];
22 | $user_room_data = $this->getRoomData($account);
23 | $room_user_data = json_decode($user_room_data[$account], true);
24 | //如果已经地主产生了, 直接下发叫地主信息
25 | if(isset($user_room_data['master']) && $user_room_data['last_chair_id']) {
26 | $this->callGameResp($room_user_data['chair_id'], $room_user_data['calltype'], $user_room_data['master'], $user_room_data['last_chair_id']);
27 | } else {
28 | if (!empty($room_user_data)) {
29 | if (!isset($room_user_data['calltype'])) {
30 | $this->setRoomUserInfoDataByKey($room_user_data, $account, 'calltype', $calltype);
31 | } else {
32 | $calltype = $room_user_data['calltype'];
33 | }
34 | $chair_id = $room_user_data['chair_id'];
35 | //广播叫地主消息
36 | $this->gameCallBroadcastResp($account, $calltype, $chair_id);
37 | //返回
38 | $this->callGameResp($chair_id, $calltype);
39 | //摸底牌操作
40 | $this->catchGameCardResp($account);
41 | }
42 | }
43 | return 0;
44 | }
45 |
46 | /**
47 | * 广播叫地主
48 | * @param $account
49 | * @param $calltype
50 | * @param $chair_id
51 | */
52 | public function gameCallBroadcastResp($account, $calltype, $chair_id)
53 | {
54 | $fds = $this->getRoomFds($account);
55 | //匹配失败, 请继续等待
56 | $msg = array('account'=>$account, 'calltype'=>$calltype, 'chair_id'=>$chair_id, 'calltime'=>time());
57 | $data = Packet::packFormat('OK', 0, $msg);
58 | $data = Packet::packEncode($data, MainCmd::CMD_GAME, SubCmd::SUB_GAME_CALL_TIPS_RESP);
59 | $this->pushToUsers($this->_params['serv'], $fds, $data);
60 | }
61 |
62 | /**
63 | * 组装抢地主返回数据
64 | * @param $chair_id
65 | * @param $calltype
66 | * @param $master
67 | * @param $last_chair_id
68 | * @return array|string
69 | */
70 | protected function callGameResp($chair_id, $calltype, $master = '', $last_chair_id = 0)
71 | {
72 | $msg = array('chair_id'=>$chair_id,'calltype'=>$calltype);
73 | if($master != '' && $last_chair_id > 0) {
74 | $msg['master'] = $master;
75 | $msg['last_chair_id'] = $last_chair_id;
76 | }
77 | $data = Packet::packFormat('OK', 0, $msg);
78 | $data = Packet::packEncode($data, MainCmd::CMD_GAME, SubCmd::SUB_GAME_CALL_RESP);
79 | $this->_params['serv']->push($this->_params['userinfo']['fd'], $data, WEBSOCKET_OPCODE_BINARY);
80 | }
81 |
82 | /**
83 | * 摸手牌操作
84 | * @param $account
85 | */
86 | protected function catchGameCardResp($account)
87 | {
88 | $room_data = $this->getRoomData($account);
89 | $infos = json_decode($room_data['uinfo'], true);
90 | if(!isset($room_data['master'])) {
91 | //加入游戏房间队列里面
92 | $calls = $accouts = array();
93 | $flag = 0;
94 | foreach ($infos as $v) {
95 | $u = json_decode($room_data[$v], true);
96 | if (isset($u['calltype'])) {
97 | $flag++;
98 | if ($u['calltype'] == 1) {
99 | $calls[] = $v;
100 | }
101 | }
102 | }
103 | if ($flag == 3) {
104 | //抢地主里随机一个人出来
105 | if (empty($calls)) {
106 | $calls = $infos;
107 | }
108 | $key = array_rand($calls, 1);
109 | $user = $calls[$key];
110 | //抓牌,合并手牌数据
111 | $user_data = json_decode($room_data[$user], true);
112 | $hand = json_decode($room_data['hand'], true);
113 | $card = array_values(array_merge($user_data['card'], $hand));
114 | $card = $this->obj_ddz->_sortCardByGrade($card);
115 | $user_data['card'] = $card;
116 | //设置地主和用户手牌数据
117 | $param = array(
118 | 'master'=>$user,
119 | $user=>json_encode($user_data)
120 | );
121 | $this->muitSetRoomData($account, $param);
122 | $this->catchGameCard($room_data, $user);
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * 抓牌返回数据
129 | * @param $room_data
130 | * @param $user
131 | * @param $infos
132 | */
133 | protected function catchGameCard($room_data, $user)
134 | {
135 | $info = json_decode($room_data[$user], true);
136 | $msg = array(
137 | 'user'=>$user,
138 | 'chair_id' => $info['chair_id'],
139 | 'hand_card' => $room_data['hand']
140 | );
141 | $data = Packet::packFormat('OK', 0, $msg);
142 | $data = Packet::packEncode($data, MainCmd::CMD_GAME, SubCmd::SUB_GAME_CATCH_BASECARD_RESP);
143 | $this->pushToUsers($this->_params['serv'], $this->getRoomFds($user), $data);
144 | }
145 | }
--------------------------------------------------------------------------------
/app/Game/Logic/GameOutCard.php:
--------------------------------------------------------------------------------
1 | _params['userinfo']['account'];
23 | $user_room_data = $this->getRoomData($account);
24 | $out_cards = $this->_params['data'];
25 | $ret = $this->playCard($user_room_data, $out_cards, $account);
26 | return $ret;
27 | }
28 |
29 | /**
30 | * 用户打牌逻辑处理
31 | * @param $user_room_data
32 | * @param $out_cards
33 | * @param $account
34 | * @return int
35 | */
36 | protected function playCard($user_room_data, $out_cards, $account)
37 | {
38 | //轮次
39 | $round = isset($user_room_data['round']) ? $user_room_data['round'] + 1 : 0;
40 | //手次
41 | $hand = isset($user_room_data['hand_num']) ? $user_room_data['hand_num'] + 1 : 1;
42 | //本轮次上一次牌型
43 | $last_chair_id = isset($user_room_data['last_chair_id']) ? $user_room_data['last_chair_id'] : 0;
44 | //本轮次上一次牌型
45 | $last_card_type = isset($user_room_data['last_card_type']) ? $user_room_data['last_card_type'] : 0;
46 | //本轮次上一次牌值
47 | $last_card = isset($user_room_data['last_card']) ? $user_room_data['last_card'] : '';
48 | //下一个出牌人椅子id
49 | $next_chair_id = $out_cards['chair_id'] + 1;
50 | $next_chair_id = ($next_chair_id > 3) ? $next_chair_id - 3 : $next_chair_id;
51 |
52 | //根据椅子查询手牌信息
53 | $my_card = json_decode($user_room_data[$account], true);
54 |
55 | //出牌牌型
56 | $card_type = '无';
57 |
58 | //验证出牌数据
59 | if($out_cards['status'] == 1) {
60 | if(count($out_cards['card']) == 0) {
61 | return $this->gameOutCard(array('status'=>1, 'msg'=>'出牌非法, 请出手牌'));
62 | } else {
63 | //判断手牌是否存在, 手牌存在继续往下执行
64 | if (!$out_cards['card'] == array_intersect($out_cards['card'], $my_card['card'])) {
65 | return $this->gameOutCard(array('status'=>2, 'msg'=>'出牌非法, 出牌数据有问题'));
66 | }
67 | //检查牌型
68 | $arr = $this->obj_ddz->checkCardType($out_cards['card']);
69 | if($arr['type'] == 0) {
70 | return $this->gameOutCard(array('status'=>3, 'msg'=>'出牌非法, 牌型有误'));
71 | } else {
72 | $card_type = $arr['type_msg'];
73 | }
74 | //如果非首轮牌, 请验证牌型, 并判断牌型是否一直, 如果打出的牌型是, 炸弹和飞机, 跳过验证, 13表示炸弹,14表示飞机
75 | if($last_card_type > 0 && !in_array($arr['type'], array(13,14)) && $last_card_type != $arr['type']) {
76 | return $this->gameOutCard(array('status'=>3, 'msg'=>'出牌非法, 和上一把牌型不符合'));
77 | }
78 | $out_cards['card_type'] = $arr['type'];
79 | //比牌大小
80 | if(!$this->obj_ddz->checkCardSize($out_cards['card'], json_decode($last_card, true))) {
81 | return $this->gameOutCard(array('status'=>4, 'msg'=>'出牌非法, 牌没有大过上家牌'));
82 | }
83 | }
84 | } else {
85 | //过牌要验证是否为首次出牌, 如果是首次出牌是不能过牌的
86 | if($hand == 1 || $last_chair_id == $out_cards['chair_id']) {
87 | return $this->gameOutCard(array('status'=>4, 'msg'=>'出牌非法, 首次出牌不能过牌操作'));
88 | }
89 | }
90 | if($out_cards['chair_id'] < 1) {
91 | return $this->gameOutCard(array('status'=>5, 'msg'=>'出牌非法, 椅子ID非法'));
92 | }
93 | //判断游戏是否结束
94 | if(count($my_card['card']) < 1) {
95 | return $this->gameOutCard(array('status'=>6, 'msg'=>'游戏结束, 所有手牌已经出完'));
96 | }
97 |
98 | //出牌逻辑
99 | if($last_card_type == 0) {
100 | //如果上一次牌型为0, 证明没有牌型, 这次手牌为开始手牌
101 | $ret = $this->roundStart($user_room_data, $out_cards, $account, $hand, $next_chair_id);
102 | \App\Game\Core\Log::show($account.":第".$ret['round'].'回合-开始');
103 | } elseif($out_cards['status'] == 0 && $last_chair_id == $next_chair_id) {
104 | //上一轮过牌, 并上一次椅子id和这一次相等, 轮次结束
105 | $this->roundEnd($account, $last_chair_id, $hand, $next_chair_id);
106 | \App\Game\Core\Log::show($account.":第".$round.'回合-结束');
107 | } else {
108 | //跟牌逻辑
109 | $this->roundFollow($out_cards, $account, $hand, $next_chair_id);
110 | $last_chair_id = $out_cards['chair_id'];
111 | \App\Game\Core\Log::show($account.":第".$round.'回合-跟牌');
112 | }
113 |
114 | //判断下个用户, 是首次出牌还是跟牌操作
115 | $is_first_round = $last_chair_id == $next_chair_id ? true : false;
116 | //设置减少手牌数据
117 | $my_card = $this->setMyCard($user_room_data, $out_cards, $account);
118 | //判断游戏是否结束
119 | $is_game_over = (count($my_card['card']) < 1) ? true : false;
120 | //计算下家牌是否能大过上一手牌
121 | // $next_card = $this->findCardsByChairId($user_room_data, $next_chair_id);
122 | // $prv_card = (isset($out_cards['card']) && count($out_cards['card']) > 0) ? $out_cards['card'] : json_decode($last_card, true);
123 | // $is_out_card = $this->obj_ddz->isPlayCard($next_card, $prv_card);
124 | // var_dump($next_card, $prv_card, $is_out_card);
125 |
126 | //并下发出牌提示
127 | $step = array(
128 | 'round'=>$round, //轮次
129 | 'hand_num'=>$hand, //首次
130 | 'chair_id'=>$out_cards['chair_id'], //出牌椅子
131 | 'account'=>$account, //出牌账号
132 | 'show_type'=>$out_cards['status'], //1,跟牌, 2, 过牌
133 | 'next_chair_id'=>$next_chair_id, //下一个出牌的椅子id
134 | 'is_first_round'=>$is_first_round, //是否为首轮, 下一个出牌人的情况
135 | 'card'=>$out_cards['card'], //本次出牌
136 | 'card_type' => $card_type, //显示牌型
137 | 'last_card'=>json_decode($last_card, true), //上次最大牌
138 | 'is_game_over' => $is_game_over //游戏是否结束
139 | );
140 |
141 | //记录一下出牌数据, 记录没步骤录像数据
142 | $this->setRoomPlayCardStep($account, 'step_'.$hand, json_encode($step));
143 | //广播打牌结果
144 | $ret = $this->gameOutCardResp($this->_params['serv'], $account, $step);
145 | //游戏结束, 重置游戏数据
146 | $this->gameOver($account, json_decode($user_room_data['uinfo'], true), $is_game_over);
147 | //记录步骤信息
148 | Log::info(json_encode($step), 'info');
149 | return $ret;
150 | }
151 |
152 | /**
153 | * 轮次开始
154 | * @param $user_room_data
155 | * @param $out_cards
156 | * @param $account
157 | * @param $hand
158 | * @param $next_chair_id
159 | * @return array
160 | */
161 | protected function roundStart($user_room_data, $out_cards, $account, $hand, $next_chair_id)
162 | {
163 | //当前轮次
164 | $round = isset($user_room_data['round']) ? $user_room_data['round'] + 1 : 1;
165 | //本轮次开始时椅子id
166 | $start_chair_id = $out_cards['chair_id'];
167 | //本轮次最大牌椅子id
168 | $last_chair_id = $out_cards['chair_id'];
169 | //本轮次最大牌椅子i牌型
170 | $last_card_type = $out_cards['card_type'];
171 | //本轮次最大牌椅子牌值
172 | $last_card = $out_cards['card'];
173 |
174 | //结果存入redis
175 | $param = array(
176 | 'round'=>$round,
177 | 'hand_num'=>$hand,
178 | 'start_chair_id'=>$start_chair_id,
179 | 'last_chair_id'=>$last_chair_id,
180 | 'last_card_type'=>$last_card_type,
181 | 'last_card'=>json_encode($last_card),
182 | 'next_chair_id'=>$next_chair_id
183 | );
184 | $this->muitSetRoomData($account, $param);
185 | return $param;
186 | }
187 |
188 | /**
189 | * 轮次结束
190 | * @param $account
191 | * @param $last_chair_id
192 | * @param $next_chair_id
193 | * @param $hand
194 | */
195 | protected function roundEnd($account, $last_chair_id, $hand, $next_chair_id)
196 | {
197 | //结果存入redis
198 | $param = array(
199 | 'start_chair_id'=>$last_chair_id,
200 | 'last_card_type'=>0,
201 | 'last_card'=>json_encode(array()),
202 | 'hand_num'=>$hand,
203 | 'next_chair_id'=>$next_chair_id
204 | );
205 | $this->muitSetRoomData($account, $param);
206 | }
207 |
208 | /**
209 | * 跟牌
210 | * @param $out_cards
211 | * @param $account
212 | * @param $next_chair_id
213 | * @param $hand
214 | */
215 | protected function roundFollow($out_cards, $account, $hand, $next_chair_id)
216 | {
217 | //跟牌
218 | $param = array();
219 | if($out_cards['status'] == 1) {
220 | //本轮次上一次最大牌椅子id
221 | $param = array(
222 | 'last_chair_id'=>$out_cards['chair_id'],
223 | 'last_card'=>json_encode($out_cards['card']),
224 | );
225 | }
226 | $param['next_chair_id'] = $next_chair_id;
227 | $param['hand_num'] = $hand;
228 | //结果存入redis
229 | $this->muitSetRoomData($account, $param);
230 | }
231 |
232 | /**
233 | * 游戏结束
234 | * @param $account
235 | * @param $uinfo
236 | * @param $is_game_over
237 | */
238 | protected function gameOver($account, $uinfo, $is_game_over) : void {
239 | if($is_game_over) {
240 | //设置游戏结束标识
241 | $this->setRoomData($account, 'is_game_over', $is_game_over);
242 | //清除数据, 进行下一轮玩牌, 随机分配
243 | $this->clearRoomNo($uinfo);
244 | }
245 | }
246 |
247 | /**
248 | * 设置我的手牌
249 | * @param $user_room_data
250 | * @param $cards
251 | * @param $account
252 | * @return mixed
253 | */
254 | protected function setMyCard($user_room_data, $cards, $account)
255 | {
256 | //根据椅子查询手牌信息
257 | $my_card = json_decode($user_room_data[$account], true);
258 | $hand_card = array_unique(array_values(array_diff($my_card['card'], $cards['card'])));
259 | if(isset($my_card['out_card'])) {
260 | $out_card = array_unique(array_values(array_merge($my_card['out_card'], $cards['card'])));
261 | } else {
262 | $out_card = $cards['card'];
263 | }
264 | $my_card['card'] = $hand_card;
265 | $my_card['out_card'] = $out_card;
266 | //写会redis
267 | $this->setRoomData($account, $account, json_encode($my_card));
268 | return $my_card;
269 | }
270 |
271 | /**
272 | * 根据椅子id找出这个一直用户的手牌
273 | * @param $user_room_data
274 | * @param $chair_id
275 | * @return array
276 | */
277 | protected function findCardsByChairId($user_room_data, $chair_id) {
278 | $uinfo = json_decode($user_room_data['uinfo'], true);
279 | $cards = array();
280 | foreach($uinfo as $v) {
281 | $d = json_decode($user_room_data[$v], true);
282 | if(isset($d['chair_id']) && $d['chair_id'] == $chair_id) {
283 | $cards = $d['card'];
284 | break;
285 | }
286 | }
287 | return $cards;
288 | }
289 |
290 | /**
291 | * 向客户端发送出牌提示响应, 单发
292 | * @param $param
293 | * @return array|string
294 | */
295 | protected function gameOutCard($param)
296 | {
297 | $data = Packet::packFormat('OK', 0, $param);
298 | $data = Packet::packEncode($data, MainCmd::CMD_GAME, SubCmd::SUB_GAME_OUT_CARD);
299 | return $data;
300 | }
301 |
302 | /**
303 | * 向客户端广播出牌响应, 群发
304 | * @param $serv
305 | * @param $account
306 | * @param $param
307 | * @return int
308 | */
309 | protected function gameOutCardResp($serv, $account, $param)
310 | {
311 | $data = Packet::packFormat('OK', 0, $param);
312 | $data = Packet::packEncode($data, MainCmd::CMD_GAME, SubCmd::SUB_GAME_OUT_CARD_RESP);
313 | $this->pushToUsers($serv, $this->getRoomFds($account), $data);
314 | //并提示成功
315 | return $this->gameOutCard(array('status'=>0, 'msg'=>'出牌成功', 'data'=>$param));
316 | }
317 | }
--------------------------------------------------------------------------------
/app/Game/Logic/GameStart.php:
--------------------------------------------------------------------------------
1 | _params['userinfo']['account'];
23 | $room_data = $this->getRoomData($account);
24 | $user_room_data = isset($room_data[$account]) ? json_decode($room_data[$account], true) : array();
25 | if($user_room_data) {
26 | //是否产生地主
27 | $master = isset($room_data['master']) ? $room_data['master'] : '';
28 | if($master) {
29 | $user_room_data['is_master'] = 1;
30 | if($master == $account) {
31 | //此人是地主
32 | $user_room_data['master'] = 1;
33 | }
34 | } else {
35 | $user_room_data['is_master'] = 0;
36 | }
37 |
38 | //轮到谁出牌了
39 | $last_chair_id = isset($room_data['last_chair_id']) ? $room_data['last_chair_id'] : 0;
40 | $next_chair_id = isset($room_data['next_chair_id']) ? $room_data['next_chair_id'] : 0;
41 | $user_room_data['is_first_round'] = false;
42 | if($next_chair_id > 0) {
43 | $user_room_data['index_chair_id'] = $next_chair_id;
44 | if($next_chair_id == $last_chair_id) {
45 | //首轮出牌
46 | $user_room_data['is_first_round'] = true;
47 | }
48 | } else {
49 | //地主首次出牌
50 | if(isset($room_data[$master])) {
51 | $master_info = json_decode($room_data[$master], true);
52 | $user_room_data['index_chair_id'] = $master_info['chair_id'];
53 | //首轮出牌
54 | $user_room_data['is_first_round'] = true;
55 | }
56 | }
57 |
58 | //判断游戏是否结束
59 | $user_room_data['is_game_over'] = isset($room_data['is_game_over']) ? $room_data['is_game_over'] : false;
60 | //进入房间成功
61 | $msg = $user_room_data;
62 | $room_data = Packet::packFormat('OK', 0, $msg);
63 | $room_data = Packet::packEncode($room_data, MainCmd::CMD_SYS, SubCmd::ENTER_ROOM_SUCC_RESP);
64 | return $room_data;
65 | } else {
66 | $room_list = $this->getGameConf('room_list');
67 | if($room_list) {
68 | //判断是否在队列里面
69 | Redis::sAdd($room_list, $this->_params['userinfo']['account']);
70 | //投递异步任务
71 | $data = Task::async('GameSyncTask', 'gameRoomMatch', [$this->_params['userinfo']['fd']]);
72 | }
73 | return 0;
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/app/Game/Logic/HeartAsk.php:
--------------------------------------------------------------------------------
1 | _params['data']['time']) ? $this->_params['data']['time'] : 0;
21 | $end_time = $this->getMillisecond();
22 | $time = $end_time - $begin_time;
23 | $data = Packet::packFormat('OK', 0, array('time'=>$time));
24 | $data = Packet::packEncode($data, MainCmd::CMD_SYS, SubCmd::HEART_ASK_RESP);
25 | return $data;
26 | }
27 |
28 | function getMillisecond()
29 | {
30 | list($t1, $t2) = explode(' ', microtime());
31 | return (float)sprintf('%.0f',(floatval($t1)+floatval($t2))*1000);
32 | }
33 | }
--------------------------------------------------------------------------------
/app/Helper/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxy918/swoft-ddz/07c42b2437691fa502e6891384f9bd4ce7d79801/app/Helper/.gitkeep
--------------------------------------------------------------------------------
/app/Helper/Functions.php:
--------------------------------------------------------------------------------
1 | _isLogin($request)) {
63 | return $response->redirect('/login');
64 | }
65 | //用户信息传递到客户端
66 | $info = $request->getCookieParams();
67 | $u = json_decode($info['USER_INFO'], true);
68 | return view('game/game', $u);
69 | }
70 |
71 | /**
72 | * login
73 | * @RequestMapping(route="/login")
74 | * @param Request $request
75 | * @param Response $response
76 | * @return Swoft\Http\Message\Concern\CookiesTrait|Response
77 | */
78 | public function login(Request $request, Response $response)
79 | {
80 | $action = $request->post('action');
81 | $account = $request->post('account');
82 | $tips = '';
83 | if($action == 'login') {
84 | if(!empty($account)) {
85 | //注册登录
86 | $uinfo = array('account'=>$account);
87 | $response = $response->withCookie('USER_INFO', json_encode($uinfo));
88 | return $response->redirect('/game');
89 | } else {
90 | $tips = '温馨提示:用户账号不能为空!';
91 | }
92 | }
93 | return view('game/login', ['tips'=>$tips]);
94 | }
95 |
96 | /**
97 | * @RequestMapping(route="/test")
98 | * @throws Throwable
99 | */
100 | public function test()
101 | {
102 | return view('game/test');
103 | }
104 |
105 | /**
106 | * 是否登录
107 | * @param Request $request
108 | * @return bool
109 | */
110 | private function _isLogin(Request $request)
111 | {
112 | $cookie_info = $request->getCookieParams();
113 | if(isset($cookie_info['USER_INFO'])) {
114 | $this->userinfo = json_decode($cookie_info['USER_INFO']);
115 | return true;
116 | } else {
117 | return false;
118 | }
119 | }
120 |
121 | /**
122 | * 广播当前服务器消息
123 | * @RequestMapping(route="/broadcast")
124 | * @return array
125 | */
126 | public function broadcast(Request $request)
127 | {
128 | $msg = $request->query('msg');
129 | //原封不动发回去
130 | if(!empty($msg)) {
131 | $msg = array('data' => $msg);
132 | } else {
133 | $msg = array('data' => 'this is a system msg');
134 | }
135 | $data = Packet::packFormat('OK', 0, $msg);
136 | $data = Packet::packEncode($data, MainCmd::CMD_SYS, SubCmd::BROADCAST_MSG_RESP);
137 | $cnt = server()->sendToAll($data, 0, 50, WEBSOCKET_OPCODE_BINARY);
138 | return ['status'=>0, 'msg'=>'给'.$cnt.'广播了消息'];
139 | }
140 |
141 | /**
142 | * 广播全服, 分布式广播, 需要安装consul注册发现服务器
143 | * @RequestMapping(route="/broadcast_to_all")
144 | * @return array
145 | */
146 | public function broadcastToAll(Request $request)
147 | {
148 | $msg = $request->query('msg');
149 | $msg = !empty($msg) ? $msg : "this is a system msg";
150 | //走consul注册发现服务器来广播消息,获取服务器列表
151 | $serviceList = $this->getServiceList();
152 | $result = [];
153 | //采用http循环发送消息
154 | foreach($serviceList as $v) {
155 | $cli = new Client($v['ip'], $v['port']);
156 | $cli->get("broadcast?msg={$msg}");
157 | $result = $cli->body;
158 | $cli->close();
159 | }
160 | return $result;
161 | }
162 |
163 | /**
164 | * get service list, 默认就是游戏网关服务器的consul服务器name
165 | *
166 | * @param string $serviceName
167 | * @return array
168 | */
169 | public function getServiceList($serviceName = 'gateway')
170 | {
171 | $cli = new Client(self::DISCOVERY_IP, self::DISCOVERY_PORT);
172 | $cli->get(sprintf(self::DISCOVERY_URI, $serviceName));
173 | $result = $cli->body;
174 | $cli->close();
175 | $services = json_decode($result, true);
176 |
177 | // 数据格式化
178 | $nodes = [];
179 | foreach ($services as $service) {
180 | if (!isset($service['Service'])) {
181 | App::warning("consul[Service] 服务健康节点集合,数据格式不不正确,Data=" . $result);
182 | continue;
183 | }
184 | $serviceInfo = $service['Service'];
185 | if (!isset($serviceInfo['Address'], $serviceInfo['Port'])) {
186 | App::warning("consul[Address] Or consul[Port] 服务健康节点集合,数据格式不不正确,Data=" . $result);
187 | continue;
188 | }
189 | $address = $serviceInfo['Address'];
190 | $port = $serviceInfo['Port'];
191 | $uri = ['ip'=>$address, 'port'=>$port];
192 | $nodes[] = $uri;
193 | }
194 | return $nodes;
195 | }
196 |
197 | /**
198 | * 录像页面
199 | * @RequestMapping(route="/camera")
200 | * @throws Throwable
201 | */
202 | public function camera()
203 | {
204 | return view('vedio/camera');
205 | }
206 |
207 | /**
208 | * 直播页面
209 | * @RequestMapping(route="/show")
210 | * @throws Throwable
211 | */
212 | public function show()
213 | {
214 | return view('vedio/show');
215 | }
216 | }
--------------------------------------------------------------------------------
/app/Http/Middleware/SomeMiddleware.php:
--------------------------------------------------------------------------------
1 | getUri()->getPath();
32 | if ($path === '/favicon.ico') {
33 | $response = Context::mustGet()->getResponse();
34 | return $response->withStatus(404);
35 | }
36 | return $handler->handle($request);
37 | }
38 | }
--------------------------------------------------------------------------------
/app/Listener/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxy918/swoft-ddz/07c42b2437691fa502e6891384f9bd4ce7d79801/app/Listener/.gitkeep
--------------------------------------------------------------------------------
/app/Listener/BeforeStartListener.php:
--------------------------------------------------------------------------------
1 | handle($request);
31 | }
32 | }
--------------------------------------------------------------------------------
/app/Rpc/Service/DbproxyService.php:
--------------------------------------------------------------------------------
1 | execProc('account_mj','sp_account_get_by_uid',array($uid));
51 | // }
52 | //
53 | // /**
54 | // * 根据uname获取用户信息
55 | // * @param string $uname
56 | // * @return array
57 | // * @throws \Swoft\Db\Exception\DbException
58 | // */
59 | // public function getLobbyInfoByName(string $uname)
60 | // {
61 | // return $this->execProc('account_mj','sp_account_get_by_username',array($uname));
62 | // }
63 | //
64 | // /**
65 | // * @param int $uid
66 | // */
67 | // public function getGameInfoByUid(int $uid)
68 | // {
69 | // return $this->execProc('game_mj','sp_user_get_by_uid',array($uid));
70 | // }
71 | //
72 | // /**
73 | // * 根据uid获取用户信息
74 | // * @param int $uid
75 | // * @return array
76 | // * @throws \Swoft\Db\Exception\DbException
77 | // */
78 | // public function getYuanbao(int $uid)
79 | // {
80 | // return $this->execProc('account_mj','sp_yuanbao_get',array($uid));
81 | // }
82 | //
83 | // /**
84 | // * 添加元宝
85 | // * @param int $uid
86 | // * @param int $quantity
87 | // * @param int $type_id
88 | // * @param int $type_id_sub
89 | // * @param int $gid
90 | // * @param int $kind_id
91 | // * @param string $summary
92 | // * @return array
93 | // * @throws \Swoft\Db\Exception\DbException
94 | // */
95 | // public function addYuanbao(int $uid, int $quantity, int $type_id = 10002, int $type_id_sub = 0 , int $gid = 0, int $kind_id = 0, string $summary = 'web exec')
96 | // {
97 | // $params = array($uid, $quantity);
98 | // $result = $this->execProc('account_mj','sp_yuanbao_add',$params);
99 | // if(isset($result[0]['quantity']) && isset($result[0]['quantity1'])) {
100 | // //写入流水
101 | // $params2 = array(
102 | // time(),
103 | // $uid,
104 | // $gid,
105 | // $type_id,
106 | // $type_id_sub,
107 | // $result[0]['quantity1'],
108 | // $result[0]['quantity'],
109 | // $quantity,
110 | // 0,
111 | // 0,
112 | // $kind_id,
113 | // $summary
114 | // );
115 | // $res = $this->execProc('log_comm_mj', 'sp_yuanbao_i', $params2);
116 | // if(!$res) {
117 | // App::error('addYuanbao_sp_yuanbao_i:' . $res);
118 | // }
119 | // }
120 | // return $result;
121 | // }
122 | //
123 | // /**
124 | // * 删除元宝
125 | // * @param int $uid
126 | // * @param int $quantity
127 | // * @param int $type_id
128 | // * @param int $type_id_sub
129 | // * @param int $gid
130 | // * @param int $kind_id
131 | // * @param string $summary
132 | // * @return array
133 | // * @throws \Swoft\Db\Exception\DbException
134 | // */
135 | // public function delYuanbao(int $uid, int $quantity, int $type_id = 10002, int $type_id_sub = 0, int $gid = 0, int $kind_id = 0, string $summary = 'web exec')
136 | // {
137 | // $params = array($uid, $quantity);
138 | // $result = $this->execProc('account_mj','sp_yuanbao_del2',$params);
139 | // if(isset($result[0]['quantity']) && isset($result[0]['quantity1'])) {
140 | // //写入流水
141 | // $params2 = array(
142 | // time(),
143 | // $uid,
144 | // $gid,
145 | // $type_id,
146 | // $type_id_sub,
147 | // $result[0]['quantity1'],
148 | // $result[0]['quantity'],
149 | // $quantity,
150 | // 0,
151 | // 0,
152 | // $kind_id,
153 | // $summary
154 | // );
155 | // $res = $this->execProc('log_comm_mj', 'sp_yuanbao_i', $params2);
156 | // if(!$res) {
157 | // App::error('delYuanbao_sp_yuanbao_i:' . $res);
158 | // }
159 | // }
160 | // return $result;
161 | // }
162 | //
163 | // /**
164 | // * 修改用户金币
165 | // * @param int $uid
166 | // * @param int $quantity
167 | // * @param int $type_id
168 | // * @param int $type_id_sub
169 | // * @param int $gid
170 | // * @param int $kind_id
171 | // * @param string $summary
172 | // * @return array
173 | // * @throws \Swoft\Db\Exception\DbException
174 | // */
175 | // public function modifyGold(int $uid, int $quantity, int $type_id = 10002, int $type_id_sub = 0, int $gid = 0, int $kind_id = 0, string $summary = 'web exec')
176 | // {
177 | // $params = array($uid, $quantity);
178 | // $result = $this->execProc('account_mj','sp_account_gold_upd2',$params);
179 | // if(isset($result[0]['quantity']) && isset($result[0]['quantity1'])) {
180 | // //写入流水
181 | // $params2 = array(
182 | // time(),
183 | // $uid,
184 | // $gid,
185 | // $type_id,
186 | // $type_id_sub,
187 | // $result[0]['quantity1'],
188 | // $result[0]['quantity'],
189 | // $quantity,
190 | // 0,
191 | // 0,
192 | // $kind_id,
193 | // $summary
194 | // );
195 | // $res = $this->execProc('log_comm_mj', 'sp_gold_i', $params2);
196 | // if(!$res) {
197 | // App::error('modifyGold_sp_gold_i:' . $res);
198 | // }
199 | // }
200 | // return $result;
201 | // }
202 | //
203 | // public function addProp(int $uid, int $prop_id , int $quantity, int $expiry_time = 2145888000, int $type_id = 10002, int $type_id_sub = 0, int $gid = 0, $cid = 0, $kind_id = 0, $version = '', $summary = 'web add')
204 | // {
205 | // $params = array($uid, $prop_id, $expiry_time, $quantity);
206 | // $result = $this->execProc('account_mj','sp_account_bag_add',$params);
207 | // if(isset($result[0]['quantity1']) && isset($result[0]['quantity2'])) {
208 | // //写入流水
209 | // $params2 = array(
210 | // $uid,
211 | // $type_id,
212 | // $type_id_sub,
213 | // $kind_id,
214 | // $gid,
215 | // $prop_id,
216 | // $quantity,
217 | // $result[0]['quantity1'],
218 | // $result[0]['quantity2'],
219 | // time(),
220 | // $cid,
221 | // $version,
222 | // 0,
223 | // $summary
224 | // );
225 | // $res = $this->execProc('log_comm_mj', 'sp_bag_i', $params2);
226 | // if(!$res) {
227 | // App::error('addProp_sp_bag_i:' . $res);
228 | // }
229 | // }
230 | // return $result;
231 | // }
232 | //
233 | // public function delProp(int $uid, int $prop_id , int $quantity, $type_id = 10002, int $type_id_sub = 0, int $gid = 0, $cid = 0, $kind_id = 0, $version = '', $summary = 'web add')
234 | // {
235 | // $params = array($uid, $prop_id, $quantity);
236 | // $result = $this->execProc('account_mj','sp_account_bag_del2',$params);
237 | // var_dump($result);
238 | // if(isset($result[0]['quantity1']) && isset($result[0]['quantity2'])) {
239 | // //写入流水
240 | // $params2 = array(
241 | // $uid,
242 | // $type_id,
243 | // $type_id_sub,
244 | // $kind_id,
245 | // $gid,
246 | // $prop_id,
247 | // $quantity,
248 | // $result[0]['quantity1'],
249 | // $result[0]['quantity2'],
250 | // time(),
251 | // $cid,
252 | // $version,
253 | // 0,
254 | // $summary
255 | // );
256 | // $res = $this->execProc('log_comm_mj', 'sp_bag_i', $params2);
257 | // if(!$res) {
258 | // App::error('addProp_sp_bag_i:' . $res);
259 | // }
260 | // }
261 | // return $result;
262 | //
263 | // }
264 | }
--------------------------------------------------------------------------------
/app/Rpc/Service/UserService.php:
--------------------------------------------------------------------------------
1 | ['list']];
31 | }
32 |
33 | /**
34 | * @param int $id
35 | *
36 | * @return bool
37 | */
38 | public function delete(int $id): bool
39 | {
40 | return false;
41 | }
42 |
43 | /**
44 | * @return void
45 | */
46 | public function returnNull(): void
47 | {
48 | return;
49 | }
50 |
51 | /**
52 | * @return string
53 | */
54 | public function getBigContent(): string
55 | {
56 | $content = Co::readFile(__DIR__ . '/big.data');
57 | return $content;
58 | }
59 |
60 | /**
61 | * Exception
62 | * @throws Exception
63 | */
64 | public function exception(): void
65 | {
66 | throw new Exception('exception version');
67 | }
68 |
69 | /**
70 | * @param string $content
71 | *
72 | * @return int
73 | */
74 | public function sendBigContent(string $content): int
75 | {
76 | return strlen($content);
77 | }
78 | }
--------------------------------------------------------------------------------
/app/Rpc/Service/UserServiceV2.php:
--------------------------------------------------------------------------------
1 | ['list'],
32 | 'v' => '1.2'
33 | ];
34 | }
35 |
36 | /**
37 | * @return void
38 | */
39 | public function returnNull(): void
40 | {
41 | return;
42 | }
43 |
44 | /**
45 | * @param int $id
46 | *
47 | * @return bool
48 | */
49 | public function delete(int $id): bool
50 | {
51 | return false;
52 | }
53 |
54 | /**
55 | * @return string
56 | */
57 | public function getBigContent(): string
58 | {
59 | $content = Co::readFile(__DIR__ . '/big.data');
60 | return $content;
61 | }
62 |
63 | /**
64 | * Exception
65 | * @throws Exception
66 | */
67 | public function exception(): void
68 | {
69 | throw new Exception('exception version2');
70 | }
71 |
72 | /**
73 | * @param string $content
74 | *
75 | * @return int
76 | */
77 | public function sendBigContent(string $content): int
78 | {
79 | return strlen($content);
80 | }
81 | }
--------------------------------------------------------------------------------
/app/Task/Listener/FinishListener.php:
--------------------------------------------------------------------------------
1 | getTaskUniqid());
28 | }
29 | }
--------------------------------------------------------------------------------
/app/Task/Task/GameSyncTask.php:
--------------------------------------------------------------------------------
1 | sCard($game_conf['room_list']);
42 | $serv = server();
43 | if($len >= 3) {
44 | //匹配成功, 下发手牌数据, 并进入房间数据
45 | $users = $users_key = $fds = array();
46 | for($i = 0; $i < 3; $i++) {
47 | $account = $redis->sPop($game_conf['room_list']);
48 | $key = sprintf($game_conf['user_bind_key'], $account);
49 | //根据账号获取fd
50 | $fds[$account] = $redis->get($key);
51 | //获取账号数
52 | $users[] = $account;
53 | }
54 | //获取房间号
55 | $room_no_key = $game_conf['user_room_no'];
56 | if($redis->exists($room_no_key)) {
57 | $room_no = $redis->get($room_no_key);
58 | $room_no++;
59 | $redis->set($room_no_key, $room_no);
60 | } else {
61 | $room_no = intval(1000001);
62 | $redis->set($room_no_key, $room_no);
63 | }
64 | //存入房间号和用户对应关系
65 | foreach($users as $v) {
66 | $user_key = sprintf($game_conf['user_room'], $v);
67 | $user_room[$user_key] = $room_no;
68 | }
69 |
70 | if(!empty($user_room)) {
71 | $redis->mset($user_room);
72 | }
73 |
74 | //随机发牌
75 | $obj = new DdzPoker();
76 | $card = $obj->dealCards($users);
77 |
78 | //存入用户信息
79 | $room_data = array(
80 | 'room_no'=>$room_no,
81 | 'hand'=>$card['card']['hand']
82 | );
83 | foreach($users as $k=>$v) {
84 | $room_data['uinfo'][] = $v;
85 | $room_data[$v] = array(
86 | 'card'=>$card['card'][$v] ,
87 | 'chair_id'=>($k+1)
88 | );
89 | }
90 | $user_room_data_key = sprintf($game_conf['user_room_data'], $room_no);
91 | $this->arrToHashInRedis($room_data, $user_room_data_key);
92 | //分别发消息给三个人
93 | foreach($users as $k=>$v) {
94 | if(isset($fds[$v])) {
95 | $data = Packet::packFormat('OK', 0, $room_data[$v]);
96 | $data = Packet::packEncode($data, MainCmd::CMD_SYS, SubCmd::ENTER_ROOM_SUCC_RESP);
97 | $serv->push($fds[$v], $data, WEBSOCKET_OPCODE_BINARY);
98 | }
99 | }
100 | } else {
101 | //匹配失败, 请继续等待
102 | $msg = array(
103 | 'status'=>'fail',
104 | 'msg'=>'人数不够3人,请耐心等待!'
105 | );
106 | $data = Packet::packFormat('OK', 0, $msg);
107 | $data = Packet::packEncode($data, MainCmd::CMD_SYS, SubCmd::ENTER_ROOM_FAIL_RESP);
108 | $serv->push($fd, $data, WEBSOCKET_OPCODE_BINARY);
109 | }
110 | }
111 |
112 | /**
113 | * 广播叫地主
114 | * @param $account
115 | * @param $calltype
116 | * @param $chair_id
117 | */
118 | public function gameCall($account, $calltype, $chair_id)
119 | {
120 | $fds = $this->_getRoomFds($account);
121 | //匹配失败, 请继续等待
122 | $msg = array(
123 | 'account'=>$account,
124 | 'calltype'=>$calltype,
125 | 'chair_id'=>$chair_id,
126 | 'calltime'=>time()
127 | );
128 | $data = Packet::packFormat('OK', 0, $msg);
129 | $data = Packet::packEncode($data, MainCmd::CMD_GAME, SubCmd::SUB_GAME_CALL_TIPS_RESP);
130 | $serv = server();
131 | $this->pushToUsers($serv, $fds, $data);
132 | }
133 |
134 | /**
135 | * 当connetions属性无效时可以使用此方法,服务器广播消息, 此方法是给所有的连接客户端, 广播消息,通过方法getClientList广播
136 | * @param $serv
137 | * @param $data
138 | * @return array
139 | */
140 | protected function pushToAll($serv, $data)
141 | {
142 | $client = array();
143 | $start_fd = 0;
144 | while(true) {
145 | $conn_list = $serv->getClientList($start_fd, 10);
146 | if ($conn_list===false or count($conn_list) === 0) {
147 | echo "BroadCast finish\n";
148 | break;
149 | }
150 | $start_fd = end($conn_list);
151 | foreach($conn_list as $fd) {
152 | //获取客户端信息
153 | $client_info = $serv->getClientInfo($fd);
154 | $client[$fd] = $client_info;
155 | if(isset($client_info['websocket_status']) && $client_info['websocket_status'] == 3) {
156 | $serv->push($fd, $data, WEBSOCKET_OPCODE_BINARY);
157 | }
158 | }
159 | }
160 | return $client;
161 | }
162 |
163 | /**
164 | * 对多用发送信息
165 | * @param $serv
166 | * @param $users
167 | * @param $data
168 | */
169 | protected function pushToUsers($serv, $users, $data)
170 | {
171 | foreach($users as $fd) {
172 | //获取客户端信息
173 | $client_info = $serv->getClientInfo($fd);
174 | $client[$fd] = $client_info;
175 | if(isset($client_info['websocket_status']) && $client_info['websocket_status'] == 3) {
176 | $serv->push($fd, $data, WEBSOCKET_OPCODE_BINARY);
177 | }
178 | }
179 | }
180 |
181 | /**
182 | * 通过accounts获取fds
183 | * @param $account
184 | * @return array
185 | */
186 | private function _getRoomFds($account)
187 | {
188 | $game_conf = config('game');
189 | $user_room_data = $game_conf['user_room_data'];
190 | $uinfo = Redis::hGet($user_room_data, $account);
191 | $uinfo = json_decode($uinfo, true);
192 | $accs = isset($uinfo['account']) ? $uinfo['account'] : array();
193 | $binds = $fds = array();
194 | if(!empty($accs)) {
195 | foreach($accs as $v) {
196 | $binds[] = sprintf($game_conf['user_bind_key'], $v);
197 | }
198 | $fds = Redis::mget($binds);
199 | }
200 | return $fds;
201 | }
202 |
203 | /**
204 | * 把php数组存入redis的hash表中
205 | * @param $arr
206 | * @param $hash_key
207 | */
208 | protected function arrToHashInRedis($arr, $hash_key)
209 | {
210 | foreach($arr as $key=>$val) {
211 | Redis::hSet($hash_key,$key, json_encode($val));
212 | }
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/app/Task/Task/SyncTask.php:
--------------------------------------------------------------------------------
1 | [1, 3, 3],
30 | 'id' => $id,
31 | 'default' => $default
32 | ];
33 | }
34 |
35 | /**
36 | * @TaskMapping()
37 | *
38 | * @param int $id
39 | *
40 | * @return bool
41 | */
42 | public function delete(int $id): bool
43 | {
44 | if ($id > 10) {
45 | return true;
46 | }
47 |
48 | return false;
49 | }
50 |
51 | /**
52 | * @TaskMapping()
53 | *
54 | * @param string $name
55 | *
56 | * @return null
57 | */
58 | public function returnNull(string $name)
59 | {
60 | return null;
61 | }
62 |
63 | /**
64 | * @TaskMapping()
65 | *
66 | * @param string $name
67 | */
68 | public function returnVoid(string $name): void
69 | {
70 | return;
71 | }
72 | }
--------------------------------------------------------------------------------
/app/Validator/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxy918/swoft-ddz/07c42b2437691fa502e6891384f9bd4ce7d79801/app/Validator/.gitkeep
--------------------------------------------------------------------------------
/app/WebSocket/GameModule.php:
--------------------------------------------------------------------------------
1 | getQueryParams();
52 | $cookie = $request->getCookieParams();
53 | $token = '';
54 | if(isset($cookie['USER_INFO'])) {
55 | $token = $cookie['USER_INFO'];
56 | } elseif(isset($query['token'])) {
57 | $token = $query['token'];
58 | }
59 | $server = server();
60 | if($token) {
61 | $uinfo = json_decode($token, true);
62 | //允许连接, 并记录用户信息
63 | $uinfo['fd'] = $fd;
64 | $user_bind_key = sprintf($game_conf['user_bind_key'], $uinfo['account']);
65 | $last_fd = (int)Redis::get($user_bind_key);
66 | //之前信息存在, 清除之前的连接
67 | if($last_fd) {
68 | //处理双开的情况
69 | $this->loginFail($last_fd, '1');
70 | $server->disconnect($last_fd);
71 | //清理redis.
72 | Redis::del($user_bind_key); //清除上一个绑定关系
73 | Redis::del(sprintf($game_conf['user_info_key'], $last_fd)); //清除上一个用户信息
74 | }
75 | //保存登陆信息
76 | Redis::set($user_bind_key, $fd, $game_conf['expire']);
77 | //设置绑定关系
78 | Redis::set(sprintf($game_conf['user_info_key'], $fd), json_encode($uinfo), $game_conf['expire']);
79 | $this->loginSuccess($server, $fd, $uinfo['account']); //登陆成功
80 | } else {
81 | $this->loginFail($fd, '2');
82 | $server->disconnect($fd);
83 | }
84 | }
85 |
86 | /**
87 | * @OnMessage()
88 | * @param Server $server
89 | * @param Frame $frame
90 | */
91 | public function onMessage(Server $server, Frame $frame)
92 | {
93 | Log::show(" Message: client #{$frame->fd} push success Mete: \n{");
94 | $data = Packet::packDecode($frame->data);
95 | if(isset($data['code']) && $data['code'] == 0 && isset($data['msg']) && $data['msg'] == 'OK') {
96 | Log::show('Recv <<< cmd='.$data['cmd'].' scmd='.$data['scmd'].' len='.$data['len'].' data='.json_encode($data['data']));
97 | //转发请求,代理模式处理,websocket路由到相关逻辑
98 | $data['serv'] = $server;
99 | //用户登陆信息
100 | $game_conf = config('game');
101 | $user_info_key = sprintf($game_conf['user_info_key'], $frame->fd);
102 | $uinfo = Redis::get($user_info_key);
103 | if($uinfo) {
104 | $data['userinfo'] = json_decode($uinfo, true);
105 | } else {
106 | $data['userinfo'] = array();
107 | }
108 | $obj = new Dispatch($data);
109 | $back = "404 Not Found swoft \n";
110 | if(!empty($obj->getStrategy())) {
111 | $back = $obj->exec();
112 | if($back) {
113 | $server->push($frame->fd, $back, WEBSOCKET_OPCODE_BINARY);
114 | }
115 | }
116 | Log::show('Tcp Strategy <<< data='.$back);
117 | } else {
118 | Log::show($data['msg']);
119 | }
120 | Log::split('}');
121 | }
122 |
123 | /**
124 | * On connection closed
125 | * - you can do something. eg. record log
126 | *
127 | * @OnClose()
128 | * @param Server $server
129 | * @param int $fd
130 | */
131 | public function onClose(Server $server, int $fd): void
132 | {
133 | //清除登陆信息变量
134 | $this->loginFail($fd, '3');
135 | }
136 |
137 | /**
138 | * 登陆成功下发协议
139 | * @param $server
140 | * @param $fd
141 | * @param $account
142 | */
143 | private function loginSuccess($server, $fd, $account)
144 | {
145 | //原封不动发回去
146 | if($server->getClientInfo($fd) !== false) {
147 | //查询用户是否在房间里面
148 | $info = $this->getRoomData($account);
149 | $data = array('status'=>'success');
150 | if(!empty($info)) {
151 | $data['is_room'] = 1;
152 | } else {
153 | $data['is_room'] = 0;
154 | }
155 | $data = Packet::packFormat('OK', 0, $data);
156 | $back = Packet::packEncode($data, MainCmd::CMD_SYS, SubCmd::LOGIN_SUCCESS_RESP);
157 | $server->push($fd, $back, WEBSOCKET_OPCODE_BINARY);
158 | }
159 | }
160 |
161 | /**
162 | * 发送登陆失败请求到客户端
163 | * @param $fd
164 | * @param string $msg
165 | */
166 | private function loginFail($fd, $msg = '')
167 | {
168 | //原封不动发回去
169 | $server = server();
170 | if($server->getClientInfo($fd) !== false) {
171 | $data = Packet::packFormat('OK', 0, array('data' => 'login fail'.$msg));
172 | $back = Packet::packEncode($data, MainCmd::CMD_SYS, SubCmd::LOGIN_FAIL_RESP);
173 | $server->push($fd, $back, WEBSOCKET_OPCODE_BINARY);
174 | }
175 | }
176 |
177 | /**
178 | * 获取房间信息
179 | * @param $account
180 | * @return array
181 | */
182 | protected function getRoomData($account)
183 | {
184 | $user_room_data = array();
185 | //获取用户房间号
186 | $room_no = $this->getRoomNo($account);
187 | //房间信息
188 | $game_key = $this->getGameConf('user_room_data');
189 | if($game_key) {
190 | $user_room_key = sprintf($game_key, $room_no);
191 | $user_room_data = Redis::hGetAll($user_room_key);
192 | }
193 | return $user_room_data;
194 | }
195 |
196 | /**
197 | * 获取用户房间号
198 | * @param $account
199 | * @return mixed
200 | */
201 | protected function getRoomNo($account)
202 | {
203 | $game_key = $this->getGameConf('user_room');
204 | //获取用户房间号
205 | $room_key = sprintf($game_key, $account);
206 | $room_no = Redis::get($room_key);
207 | return $room_no ? $room_no : 0;
208 | }
209 |
210 | /**
211 | * 返回游戏配置
212 | * @param string $key
213 | * @return string
214 | */
215 | protected function getGameConf($key = '') {
216 | $conf = config('game');
217 | if(isset($conf[$key])) {
218 | return $conf[$key];
219 | } else {
220 | return '';
221 | }
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/app/WebSocket/VedioModule.php:
--------------------------------------------------------------------------------
1 | fd}...data:".$frame->data."\n";
68 | if(!is_numeric($frame->data)) {
69 | //如果是录像数据, 发送二进制数据
70 | $ret = server()->sendToAll($frame->data, 0, 50);
71 | var_dump($ret);
72 | }
73 | }
74 |
75 |
76 | /**
77 | * On connection closed
78 | * - you can do something. eg. record log
79 | *
80 | * @OnClose()
81 | * @param Server $server
82 | * @param int $fd
83 | */
84 | public function onClose(Server $server, int $fd): void
85 | {
86 | //清除登陆信息变量
87 | echo "vedio close #{$fd}...";
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/bean.php:
--------------------------------------------------------------------------------
1 | [
21 | 'path' => __DIR__ . '/../config',
22 | // 'env' => 'dev',
23 | ],
24 | 'httpServer' => [
25 | 'class' => HttpServer::class,
26 | 'port' => 18306,
27 | 'listener' => [
28 | 'rpc' => bean('rpcServer')
29 | ],
30 | 'process' => [
31 | // 'monitor' => bean(MonitorProcess::class)
32 | ],
33 | 'on' => [
34 | SwooleEvent::TASK => bean(SyncTaskListener::class), // Enable sync task
35 | SwooleEvent::TASK => bean(TaskListener::class), // Enable task must task and finish event
36 | SwooleEvent::FINISH => bean(FinishListener::class)
37 | ],
38 | /* @see HttpServer::$setting */
39 | 'setting' => [
40 | 'task_worker_num' => 12,
41 | 'task_enable_coroutine' => true
42 | ]
43 | ],
44 | 'httpDispatcher' => [
45 | // Add global http middleware
46 | 'middlewares' => [
47 | // Allow use @View tag
48 | // \Swoft\View\Middleware\ViewMiddleware::class,
49 | \App\Http\Middleware\SomeMiddleware::class,
50 | // \Swoft\Smarty\Middleware\SmartyMiddleware::class
51 | ],
52 | ],
53 | 'db' => [
54 | 'class' => Database::class,
55 | 'dsn' => 'mysql:dbname=accounts_mj;host=192.168.22.34:3307',
56 | 'username' => 'web',
57 | 'password' => '111www',
58 | 'charset' => 'utf8',
59 | ],
60 | 'db.pool' => [
61 | 'class' => Pool::class,
62 | 'database' => bean('db'),
63 | 'minActive' => 10,
64 | 'maxActive' => 20,
65 | 'maxWait' => 0,
66 | 'maxWaitTime' => 0,
67 | 'maxIdleTime' => 60,
68 | ],
69 | 'migrationManager' => [
70 | 'migrationPath' => '@app/Migration',
71 | ],
72 | 'redis' => [
73 | 'class' => RedisDb::class,
74 | 'host' => 'redis',
75 | 'port' => 6379,
76 | 'database' => 0,
77 | 'option' => [
78 | 'prefix' => ''
79 | ]
80 | ],
81 | 'redis.pool' => [
82 | 'class' => \Swoft\Redis\Pool::class,
83 | 'redisDb' => \bean('redis'),
84 | 'minActive' => 10,
85 | 'maxActive' => 20,
86 | 'maxWait' => 0,
87 | 'maxWaitTime' => 0,
88 | 'maxIdleTime' => 60,
89 | ],
90 | 'user' => [
91 | 'class' => ServiceClient::class,
92 | 'host' => '127.0.0.1',
93 | 'port' => 18307,
94 | 'setting' => [
95 | 'timeout' => 0.5,
96 | 'connect_timeout' => 1.0,
97 | 'write_timeout' => 10.0,
98 | 'read_timeout' => 0.5,
99 | ],
100 | 'packet' => bean('rpcClientPacket')
101 | ],
102 | 'user.pool' => [
103 | 'class' => ServicePool::class,
104 | 'client' => bean('user')
105 | ],
106 | 'rpcServer' => [
107 | 'class' => ServiceServer::class,
108 | ],
109 | 'wsServer' => [
110 | 'class' => WebSocketServer::class,
111 | 'port' => 18308,
112 | 'on' => [
113 | // 开启处理http请求支持
114 | SwooleEvent::REQUEST => bean(RequestListener::class),
115 | // 启用任务必须添加 task, finish 事件处理
116 | SwooleEvent::TASK => bean(TaskListener::class),
117 | SwooleEvent::FINISH => bean(FinishListener::class)
118 | ],
119 | 'listener' => [
120 | // 引入 tcpServer
121 | 'tcp' => \bean('tcpServer')
122 | ],
123 | 'debug' => env('SWOFT_DEBUG', 0),
124 | /* @see WebSocketServer::$setting */
125 | 'setting' => [
126 | 'log_file' => alias('@runtime/swoole.log'),
127 | 'document_root' => dirname(__DIR__) . '/public/',
128 | 'enable_static_handler' => true,
129 | 'worker_num' => 2,
130 | // 任务需要配置 task worker
131 | 'task_worker_num' => 4,
132 | 'task_enable_coroutine' => true,
133 | 'max_request' => 10000,
134 | 'package_max_length' => 20480
135 | ],
136 | ],
137 | 'tcpServer' => [
138 | 'port' => 18309,
139 | 'debug' => 1,
140 | 'on' => [
141 | SwooleEvent::RECEIVE => bean(App\Common\TcpReceiveListener::class)
142 | ],
143 | 'setting' => [
144 | 'log_file' => alias('@runtime/swoole.log'),
145 | 'worker_num' => 2,
146 | // 任务需要配置 task worker
147 | 'task_worker_num' => 4,
148 | 'task_enable_coroutine' => true,
149 | 'max_request' => 10000,
150 | 'package_max_length' => 20480
151 | ],
152 | ],
153 | 'cliRouter' => [
154 | // 'disabledGroups' => ['demo', 'test'],
155 | ],
156 | 'processPool' => [
157 | 'class' => \Swoft\Process\ProcessPool::class,
158 | 'workerNum' => 3
159 | ],
160 | 'lineFormatter' => [
161 | 'format' => '%datetime% [%level_name%] [%channel%] [%event%] [tid:%tid%] [cid:%cid%] [traceid:%traceid%] [spanid:%spanid%] [parentid:%parentid%] %messages%',
162 | 'dateFormat' => 'Y-m-d H:i:s',
163 | ],
164 | 'noticeHandler' => [
165 | 'class' => \Swoft\Log\Handler\FileHandler::class,
166 | 'logFile' => '@runtime/logs/notice.log',
167 | 'formatter' => \bean('lineFormatter'),
168 | 'levels' => 'notice,info,debug,trace',
169 | ],
170 | 'applicationHandler' => [
171 | 'class' => \Swoft\Log\Handler\FileHandler::class,
172 | 'logFile' => '@runtime/logs/error.log',
173 | 'formatter' => \bean('lineFormatter'),
174 | 'levels' => 'error,warning',
175 | ],
176 | 'logger' => [
177 | 'flushRequest' => false,
178 | 'enable' => true,
179 | 'handlers' => [
180 | 'application' => \bean('applicationHandler'),
181 | 'notice' => \bean('noticeHandler'),
182 | ],
183 | ]
184 | ];
185 |
--------------------------------------------------------------------------------
/bin/bootstrap.php:
--------------------------------------------------------------------------------
1 | 300000,
9 | ]);
10 |
11 | // Run application
12 | (new \App\Application())->run();
13 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "swoft/swoft",
3 | "type": "project",
4 | "keywords": [
5 | "php",
6 | "swoole",
7 | "swoft",
8 | "swoft-game"
9 | ],
10 | "description": "Modern High performance AOP and Coroutine PHP Framework",
11 | "license": "Apache-2.0",
12 | "require": {
13 | "php": ">7.1",
14 | "ext-pdo": "*",
15 | "ext-redis": "*",
16 | "ext-json": "*",
17 | "ext-mbstring": "*",
18 | "swoft/db": "~2.0.0",
19 | "swoft/i18n": "~2.0.0",
20 | "swoft/view": "~2.0.0",
21 | "swoft/task": "~2.0.0",
22 | "swoft/redis": "~2.0.0",
23 | "swoft/framework": "~2.0.0",
24 | "swoft/http-server": "~2.0.0",
25 | "swoft/rpc-client": "~2.0.0",
26 | "swoft/rpc-server": "~2.0.0",
27 | "swoft/websocket-server": "~2.0.0",
28 | "swoft/tcp-server": "~2.0.0",
29 | "swoft/process": "^2.0",
30 | "swoft/apollo": "~2.0.0",
31 | "swoft/consul": "~2.0.0",
32 | "swoft/limiter": "~2.0.0",
33 | "swoft/breaker": "~2.0.0",
34 | "swoft/devtool": "~2.0.0",
35 | "php-amqplib/php-amqplib": "^2.9",
36 | "rybakit/msgpack": "^0.5.4"
37 | },
38 | "require-dev": {
39 | "swoft/swoole-ide-helper": "dev-master",
40 | "phpunit/phpunit": "^7.5"
41 | },
42 | "autoload": {
43 | "psr-4": {
44 | "App\\": "app/"
45 | },
46 | "files": [
47 | "app/Helper/Functions.php"
48 | ]
49 | },
50 | "autoload-dev": {
51 | "psr-4": {
52 | "AppTest\\Testing\\": "test/testing",
53 | "AppTest\\Unit\\": "test/unit"
54 | }
55 | },
56 | "scripts": {
57 | "post-root-package-install": [
58 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
59 | ],
60 | "test": "./vendor/bin/phpunit -c phpunit.xml",
61 | "cs-fix": "./vendor/bin/php-cs-fixer fix $1"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/config/base.php:
--------------------------------------------------------------------------------
1 | 'Swoft framework 2.0',
4 | 'debug' => env('SWOFT_DEBUG', 0),
5 | ];
6 |
--------------------------------------------------------------------------------
/config/game.php:
--------------------------------------------------------------------------------
1 | 'user:info:%s', //用户信息redis的key,fd对应用户信息
8 | 'user_bind_key' => 'user:bind:%s', //用户绑定信息和fd绑定key,里面存是根据fd存入account和fd绑定关系
9 | 'expire' => 1 * 24 * 60 * 60, //设置key过期时间, 设置为1天
10 | 'room_list'=> 'user:room:list', //用户进入房间队列
11 | 'user_room_no' => 'user:room:no', //用户自增房间号
12 | 'user_room' => 'user:room:map:%s', //用户和房间映射关系
13 | 'user_room_data' => 'user:room:data:%s', //用户游戏房间数据
14 | 'user_room_play' => 'user:room:play:%s', //用户游戏房间打牌步骤数据
15 | ];
--------------------------------------------------------------------------------
/images/ai.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxy918/swoft-ddz/07c42b2437691fa502e6891384f9bd4ce7d79801/images/ai.jpg
--------------------------------------------------------------------------------
/images/demo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxy918/swoft-ddz/07c42b2437691fa502e6891384f9bd4ce7d79801/images/demo1.png
--------------------------------------------------------------------------------
/images/demo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxy918/swoft-ddz/07c42b2437691fa502e6891384f9bd4ce7d79801/images/demo2.png
--------------------------------------------------------------------------------
/public/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxy918/swoft-ddz/07c42b2437691fa502e6891384f9bd4ce7d79801/public/.keep
--------------------------------------------------------------------------------
/public/client/Const.js:
--------------------------------------------------------------------------------
1 | /** 主命令字定义 **/
2 | var MainCmd = {
3 | CMD_SYS : 1, /** 系统类(主命令字)- 客户端使用 **/
4 | CMD_GAME : 2, /** 游戏类(主命令字)- 客户端使用 **/
5 | }
6 |
7 | /** 子命令字定义 **/
8 | var SubCmd = {
9 | //系统子命令字,对应MainCmd.CMD_SYS
10 | LOGIN_FAIL_RESP : 100,
11 | HEART_ASK_REQ : 101,
12 | HEART_ASK_RESP : 102,
13 | BROADCAST_MSG_REQ : 103,
14 | BROADCAST_MSG_RESP : 104,
15 |
16 | //游戏逻辑子命令字,对应MainCmd.CMD_GAME
17 | SUB_GAME_START_REQ : 1, //游戏开始---> CGameStart
18 | SUB_GAME_START_RESP : 2, //游戏开始---> CGameStart
19 | SUB_USER_INFO_RESP : 3, //用户信息 ------> CUserInfo
20 | SUB_GAME_SEND_CARD_RESP : 4, //发牌 ------> CGameSendCard
21 | SUB_GAME_CALL_TIPS_RESP : 5, //叫地主提示(广播) --> CGameCall
22 | SUB_GAME_CALL_REQ : 6, //叫地主请求 --> CGameCallReq
23 | SUB_GAME_CALL_RESP : 7, //叫地主请求返回--CGameCallResp
24 | SUB_GAME_DOUBLE_TIPS_RESP : 8, //加倍提示(广播) --> CGameDouble
25 | SUB_GAME_DOUBLE_REQ : 9, //加倍请求--> CGameDoubleReq
26 | SUB_GAME_DOUBLE_RESP : 10, //加倍请求返回----> CGameDoubleResp
27 | SUB_GAME_CATCH_BASECARD_RESP : 11, //摸底牌 ---> CGameCatchBaseCard
28 | SUB_GAME_OUT_CARD : 12, //出牌提示 --> CGameOutCard
29 | SUB_GAME_OUT_CARD_REQ : 13, //出牌请求 --> CGameOutCardReq
30 | SUB_GAME_OUT_CARD_RESP : 14, //出牌返回 --> CGameOutCardResp
31 |
32 | CHAT_MSG_REQ : 213, //聊天消息请求,客户端使用
33 | CHAT_MSG_RESP : 214, //聊天消息响应,服务端使用
34 | }
35 |
36 |
37 | /**
38 | * 路由规则,key主要命令字=》array(子命令字对应策略类名)
39 | * 每条客户端对应的请求,路由到对应的逻辑处理类上处理
40 | *
41 | */
42 | var Route = {
43 | 1 : {
44 | 100 : 'loginFail', //登陆失败
45 | 105 : 'loginSuccess', //登陆成功
46 | 102 : 'heartAsk', //心跳处理
47 | 104 : 'broadcast', //广播消息
48 | 106 : 'enterRoomFail', //进入房间失败
49 | 107 : 'enterRoomSucc', //进入房间成功
50 | },
51 | 2 : {
52 | 2 : 'gameStart', //获取卡牌
53 | 214 : 'chatMsg',
54 | 3 : 'userInfo', //显示用户信息
55 | 5 : 'gameCallTips', //叫地主广播
56 | 7 : 'gameCall', //叫地主返回
57 | 11 : 'gameCatchCardTips', //摸底牌
58 | 12 : 'gameOutCard', //出牌广播
59 | 14 : 'gameOutCardResp', //出牌响应
60 | },
61 | }
62 |
63 | /**
64 | * 花色类型
65 | */
66 | var CardType = {
67 | HEITAO : 0, //黑桃
68 | HONGTAO : 1, //红桃
69 | MEIHUA : 2, //梅花
70 | FANGKUAI : 3, //方块
71 | XIAOWANG : 4, //小王
72 | DAWANG : 5, //大王
73 | }
74 | /**
75 | * 牌显示出来的值
76 | */
77 | var CardVal = {
78 | CARD_SAN : '3', //牌值3
79 | CARD_SI : '4', //牌值4
80 | CARD_WU : '5', //牌值5
81 | CARD_LIU : '6', //牌值6
82 | CARD_QI : '7', //牌值7
83 | CARD_BA : '8', //牌值8
84 | CARD_JIU : '9', //牌值9
85 | CARD_SHI : '10', //牌值10
86 | CARD_J : 'J', //牌值J
87 | CARD_Q : 'Q', //牌值Q
88 | CARD_K : 'K', //牌值K
89 | CARD_A : 'A', //牌值A
90 | CARD_ER : '2', //牌值2
91 | CARD_XIAOWANG : 'w', //牌值小王
92 | CARD_DAWANG : 'W', //牌值大王
93 | }
--------------------------------------------------------------------------------
/public/client/Init.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 初始化类,websock服务器类
3 | */
4 |
5 | var Init = {
6 | ws : null,
7 | url : "",
8 | timer : 0,
9 | reback_times : 100, //断线重连次数
10 | dubug : true,
11 |
12 | //启动websocket
13 | webSock: function (url) {
14 | this.url = url;
15 | ws = new WebSocket(url);
16 | ws.binaryType = "arraybuffer"; //设置为2进制类型 webSocket.binaryType = "blob" ;
17 | var obj = this;
18 | //连接回调
19 | ws.onopen = function(evt) {
20 | Req.heartBeat(obj);
21 | //清除定时器
22 | clearInterval(obj.timer);
23 | //获取用户状态
24 | obj.log('系统提示: 连接服务器成功');
25 | };
26 |
27 | //消息回调
28 | ws.onmessage = function(evt) {
29 | if(!evt.data) return ;
30 | var total_data = new DataView(evt.data);
31 | var total_len = total_data.byteLength;
32 | if(total_data.byteLength < 4){
33 | obj.log('系统提示: 数据格式有问题');
34 | ws.close();
35 | return ;
36 | }
37 |
38 | //进行粘包处理
39 | var off = 0;
40 | var guid = body = '';
41 | while(total_len > off) {
42 | var len = total_data.getUint32(off);
43 | var data = evt.data.slice(off, off + len + 4);
44 | //解析body
45 | body = Packet.msgunpack(data);
46 | //转发响应的请求
47 | obj.recvCmd(body);
48 | off += len + 4;
49 | }
50 |
51 | };
52 | //关闭回调
53 | ws.onclose = function(evt) {
54 | //断线重新连接
55 | obj.timer = setInterval(function () {
56 | if(obj.reback_times == 0) {
57 | clearInterval(obj.timer);
58 | clearInterval(Req.timer);
59 | } else {
60 | obj.reback_times--;
61 | obj.webSock(obj.url);
62 | }
63 | },5000);
64 | obj.log('系统提示: 连接断开');
65 | };
66 | //socket错误回调
67 | ws.onerror = function(evt) {
68 | obj.log('系统提示: 服务器错误'+evt.type);
69 | };
70 | this.ws = ws;
71 | return this;
72 | },
73 |
74 | //处理消息回调命令字
75 | recvCmd: function(body) {
76 | console.log('debub data'+body);
77 | console.log(body);
78 | var len = body['len'];
79 | var cmd = body['cmd'];
80 | var scmd = body['scmd'];
81 | var data = body['data'];
82 | this.log('websocket Recv <<< len='+len+" cmd="+cmd+" scmd="+scmd);
83 | //路由到处理地方
84 | var func = Route[cmd][scmd];
85 | var str = 'Resp.'+func;
86 | if(func) {
87 | if(typeof(eval(str)) == 'function') {
88 | eval("Resp."+func+"(data)");
89 | } else {
90 | document.getElementById('msgText').innerHTML += func+':'+JSON.stringify(data) + '\n';
91 | }
92 | } else {
93 | this.log('func is valid');
94 | }
95 |
96 | this.log("websocket Recv body <<< func="+func);
97 | this.log(body);
98 | },
99 |
100 | //打印日志方法
101 | log: function(msg) {
102 | if(this.dubug) {
103 | console.log(msg);
104 | }
105 | },
106 |
107 | //发送数据
108 | send: function(data, cmd, scmd) {
109 | //this.ws.close();
110 | this.log("websocket Send >>> cmd="+cmd+" scmd="+scmd+" data=");
111 | this.log(data);
112 | var pack_data = Packet.msgpack(data, cmd, scmd);
113 | //组装数据
114 | if(this.ws.readyState == this.ws.OPEN) {
115 | this.ws.send(pack_data);
116 | }
117 | }
118 | }
--------------------------------------------------------------------------------
/public/client/Packet.js:
--------------------------------------------------------------------------------
1 | var Packet = {
2 | //jons数据打包成二进制数据
3 | encode : function(data, cmd, scmd) {
4 | var data = JSON.stringify(data);
5 | var len = data.length + 6;
6 | var buf = new ArrayBuffer(len); // 每个字符占用1个字节
7 | var buff_data = new DataView(buf, 0, len);
8 | var str_len = data.length;
9 | buff_data.setUint32(0, str_len+2);
10 | buff_data.setUint8(4, cmd);
11 | buff_data.setUint8(5, scmd);
12 | for (var i = 0; i < str_len; i++) {
13 | buff_data.setInt8(i+6, data.charCodeAt(i));
14 | }
15 | return buf;
16 | },
17 |
18 | //二进制数据解包成二进制数据
19 | decode : function(buff) {
20 | var body = '';
21 | var len = new DataView(buff, 0, 4).getUint32(0);
22 | var body_data = new DataView(buff, 4, len);
23 | //解析cmd
24 | var cmd = body_data.getUint8(0);
25 | var scmd = body_data.getUint8(1);
26 |
27 | //解析body
28 | for(var i = 2; i < body_data.byteLength; i++) {
29 | body += String.fromCharCode(body_data.getUint8(i));
30 | }
31 | //console.log("data decode >>> len="+len+" cmd="+cmd+" scmd="+scmd+" data="+body);
32 | var body = JSON.parse(body);
33 | body["cmd"] = cmd;
34 | body["scmd"] = scmd;
35 | return body;
36 | },
37 |
38 | encodeUTF8: function(str){
39 | var temp = "",rs = "";
40 | for( var i=0 , len = str.length; i < len; i++ ){
41 | temp = str.charCodeAt(i).toString(16);
42 | rs += "\\u"+ new Array(5-temp.length).join("0") + temp;
43 | }
44 | return rs;
45 | },
46 |
47 | decodeUTF8: function(str){
48 | return str.replace(/(\\u)(\w{4}|\w{2})/gi, function($0,$1,$2){
49 | return String.fromCharCode(parseInt($2,16));
50 | });
51 | },
52 |
53 | //使用msgpack解包arraybuf数据
54 | msgunpack: function(buff) {
55 | var body = '';
56 | var len = new DataView(buff, 0, 4).getUint32(0);
57 | var body_data = new DataView(buff, 4, len);
58 | //解析cmd
59 | var cmd = body_data.getUint8(0);
60 | var scmd = body_data.getUint8(1);
61 |
62 | //解析body
63 | for(var i = 2; i < body_data.byteLength; i++) {
64 | body += String.fromCharCode(body_data.getUint8(i));
65 | }
66 | //console.log("data msgpack decode >>> cmd="+cmd+" scmd="+scmd+" len="+len+" data="+body);
67 | var body = msgpack.unpack(body);
68 | body["cmd"] = cmd;
69 | body["scmd"] = scmd;
70 | body["len"] = len;
71 | return body;
72 | },
73 |
74 | //使用packmsg打包object数据对象
75 | msgpack: function(data, cmd, scmd) {
76 | //var dt = {};
77 | //dt.data = data;
78 | var data_buff = msgpack.pack(data);
79 | var str_buff = String.fromCharCode.apply(null, new Uint8Array(data_buff));
80 | var len = str_buff.length + 6;
81 | var buf = new ArrayBuffer(len); // 每个字符占用1个字节
82 | var buff_data = new DataView(buf, 0, len);
83 | var str_len = str_buff.length;
84 | buff_data.setUint32(0, str_len + 2);
85 | buff_data.setUint8(4, cmd);
86 | buff_data.setUint8(5, scmd);
87 |
88 | for (var i = 0; i < str_len; i++) {
89 | buff_data.setInt8(i+6, str_buff.charCodeAt(i));
90 | }
91 | //console.log("data msgpack encode >>> cmd="+cmd+" scmd="+scmd+" len="+len+" data=");
92 | //console.log(data);
93 | return buf;
94 | }
95 | }
--------------------------------------------------------------------------------
/public/client/Req.js:
--------------------------------------------------------------------------------
1 | /**发请求命令字处理类*/
2 |
3 | var Req = {
4 | //定时器
5 | timer : 0,
6 |
7 | //发送心跳
8 | heartBeat:function(obj) {
9 | this.timer = setInterval(function () {
10 | if(obj.ws.readyState == obj.ws.OPEN) {
11 | var data = {};
12 | data['time'] = (new Date()).valueOf()
13 | obj.send(data, MainCmd.CMD_SYS, SubCmd.HEART_ASK_REQ);
14 | } else {
15 | clearInterval(this.timer);
16 | }
17 | }, 600000);
18 | },
19 |
20 | //游戏开始
21 | GameStart: function(obj,data) {
22 | var data = {};
23 | obj.send(data, MainCmd.CMD_GAME, SubCmd.SUB_GAME_START_REQ);
24 | },
25 |
26 | //抢地主
27 | GameCall: function(obj,status) {
28 | var data = {"type": status};
29 | obj.send(data, MainCmd.CMD_GAME, SubCmd.SUB_GAME_CALL_REQ);
30 | },
31 |
32 | //玩游戏
33 | PlayGame: function(obj,data) {
34 | obj.send(data, MainCmd.CMD_GAME, SubCmd.SUB_GAME_OUT_CARD_REQ);
35 | },
36 |
37 | //聊天消息
38 | ChatMsg: function(obj, data) {
39 | var data = {data};
40 | obj.send(data, MainCmd.CMD_GAME, SubCmd.CHAT_MSG_REQ);
41 | },
42 | }
--------------------------------------------------------------------------------
/public/client/Resp.js:
--------------------------------------------------------------------------------
1 | /**响应服务器命令字处理类*/
2 |
3 | var Resp = {
4 | //心跳响应
5 | heartAsk: function(data) {
6 | this.log(data);
7 | this.showTips('心跳数据:');
8 | },
9 |
10 | //登录失败
11 | loginFail: function(data) {
12 | this.log(data);
13 | this.showTips('登录失败:');
14 | //跳转登陆页面
15 | document.location.href = "login";
16 | },
17 |
18 | //登录成功响应
19 | loginSuccess: function(data) {
20 | this.log(data);
21 | this.showTips('登录成功');
22 | //如果用户在房间里, 直接进入房间
23 | if(data.is_room == 1) {
24 | //进入房间请求
25 | Req.GameStart(obj,{});
26 | }
27 | },
28 |
29 | //游戏开始响应
30 | gameStart: function(data) {
31 | this.log(data);
32 | this.showTips('游戏开始');
33 | },
34 |
35 | //用户信息
36 | userInfo: function(data) {
37 | this.log(data);
38 | this.showTips('用户信息');
39 | },
40 |
41 | //叫地主
42 | gameCall: function(data) {
43 | this.log(data);
44 | this.showTips('我叫地主成功');
45 | document.getElementById('call').disabled = true;
46 | document.getElementById('nocall').disabled = true;
47 | },
48 |
49 | //叫地主广播
50 | gameCallTips: function(data) {
51 | this.log(data);
52 | if(data.calltype == 1) {
53 | var tips = data.account+'叫地主';
54 | } else {
55 | var tips = data.account+'不叫';
56 | }
57 | this.showTips('广播:'+tips);
58 | },
59 |
60 | //摸底牌
61 | gameCatchCardTips: function(data) {
62 | this.log(data);
63 | this.showTips('广播:'+data.user+'摸底牌'+data.hand_card);
64 | //摸完底牌, 重新构造牌, 这里偷懒, 重新触发开始游戏
65 | Req.GameStart(obj,{});
66 | },
67 |
68 |
69 | //聊天数据
70 | chatMsg: function(data) {
71 | this.log(data);
72 | this.showTips('聊天内容是:'+JSON.stringify(data));
73 | },
74 |
75 | //进入房间失败
76 | enterRoomFail: function(data) {
77 | this.log(data);
78 | this.showTips('进入房间失败:'+data.msg);
79 | },
80 |
81 | //进入房间成功,解锁按钮
82 | enterRoomSucc: function(data) {
83 | this.log(data);
84 | this.showTips('进入房间成功:'+JSON.stringify(data));
85 | var card = data.card;
86 | var chair_id = data.chair_id;
87 | var is_master = data.is_master;
88 | var is_game_over = data.is_game_over;
89 | info = data
90 | if(typeof(data.calltype) == 'undefined') {
91 | document.getElementById('call').disabled = false;
92 | document.getElementById('nocall').disabled = false;
93 | } else {
94 | document.getElementById('call').disabled = true;
95 | document.getElementById('nocall').disabled = true;
96 | }
97 |
98 | //显示牌
99 | if(card && chair_id) {
100 | //循环展现牌
101 | var show_card = '';
102 | for(var k in card) {
103 | show_card += ' '+this.getCard(card[k])+' ';
104 | }
105 | var id = 'chair_'+chair_id;
106 | document.getElementById(id).innerHTML = show_card;
107 | }
108 |
109 | //是否为地主
110 | if(is_master == 1) {
111 | if(typeof(data.master) != 'undefined') {
112 | document.getElementById('master').innerHTML = '(地主)-'+chair_id+'号位置';
113 | } else {
114 | document.getElementById('master').innerHTML = '(农民)-'+chair_id+'号位置';
115 | }
116 | }
117 |
118 | //判断游戏是否结束
119 | if(is_game_over) {
120 | this.showTips('游戏结束');
121 | } else {
122 | //轮到谁出来, 就解锁谁的按钮
123 | if(typeof(data.index_chair_id) != 'undefined' && info.chair_id == data.index_chair_id) {
124 | //解锁打牌按钮
125 | document.getElementById('play').disabled = false;
126 | document.getElementById('pass').disabled = false;
127 | var tips = data.is_first_round ? '请首次出牌' : '请跟牌';
128 | this.showTips(tips);
129 | } else {
130 | document.getElementById('play').disabled = true;
131 | document.getElementById('pass').disabled = true;
132 | }
133 | }
134 | },
135 |
136 | //出牌提示
137 | gameOutCard: function(data) {
138 | this.log(data);
139 | this.showTips('出牌提示:'+data.msg);
140 | if(data.status == 0) {
141 | //移除当前牌元素
142 | var obj_box = document.getElementsByName("handcard");
143 | var obj_item = [];
144 | for(k in obj_box){
145 | if(obj_box[k].checked){
146 | obj_item[k] = obj_box[k].parentNode;
147 | }
148 | }
149 | //循环删除
150 | for(k in obj_item){
151 | obj_item[k].remove(this);
152 | }
153 | }
154 | },
155 |
156 | //出牌广播响应
157 | gameOutCardResp: function(data) {
158 | //判断游戏是否结束
159 | if(data.is_game_over) {
160 | this.showTips('广播:游戏结束,'+data.account+'胜利, 请点击"开始游戏",进行下一轮游戏');
161 | //手牌重置
162 | document.getElementById('chair_1').innerHTML = '';
163 | document.getElementById('chair_2').innerHTML = '';
164 | document.getElementById('chair_3').innerHTML = '';
165 | document.getElementById('last_card').innerHTML = '';
166 | document.getElementById('out_card').innerHTML = '';
167 | document.getElementById('play').disabled = true;
168 | document.getElementById('pass').disabled = true;
169 | } else {
170 | this.log(data);
171 | var play = data.show_type == 1 ? '跟牌' : '过牌';
172 | if(data.last_card == null || data.last_card.length < 1) {
173 | play = '出牌';
174 | }
175 | this.showTips('广播: 第'+data.round+'回合,第'+data.hand_num+'手出牌, '+data.account+play+', 上次牌值是'+data.last_card+', 本次出牌值是'+data.card+', 本次出牌型是'+data.card_type);
176 | this.showPlayCard(data.last_card,data.card);
177 |
178 | //自己出牌按钮变灰
179 | if(info.chair_id == data.next_chair_id) {
180 | document.getElementById('play').disabled = false;
181 | document.getElementById('pass').disabled = false;
182 | //提示下一个跟牌操作
183 | var tips = data.is_first_round ? '请首次出牌' : '请跟牌';
184 | this.showTips(tips);
185 | } else {
186 | document.getElementById('play').disabled = true;
187 | document.getElementById('pass').disabled = true;
188 | }
189 | }
190 |
191 | },
192 |
193 | //广播消息响应
194 | broadcast: function(data) {
195 | this.log(data);
196 | this.showTips("广播:消息,"+JSON.stringify(data));
197 | },
198 |
199 | //显示打牌过程
200 | showPlayCard: function(last_card, out_card) {
201 | document.getElementById('last_card').innerHTML = '';
202 | document.getElementById('out_card').innerHTML = '';
203 | if(last_card != null && typeof(last_card) == 'object' && last_card.length > 0) {
204 | var l = '';
205 | for(k in last_card) {
206 | l += ''+this.getCard(last_card[k])+' ';
207 | }
208 | document.getElementById('last_card').innerHTML = l;
209 | }
210 | if(out_card != null && typeof(out_card) == 'object' && out_card.length > 0) {
211 | var n = '';
212 | for(j in out_card) {
213 | n += ''+this.getCard(out_card[j])+' ';
214 | }
215 | document.getElementById('out_card').innerHTML = n;
216 | }
217 |
218 | },
219 |
220 | //构造牌
221 | getCard: function(card_val) {
222 | var card = '';
223 | var color = parseInt(card_val / 16);
224 | if(color == CardType.HEITAO) {
225 | card += '♠';
226 | } else if(color == CardType.HONGTAO) {
227 | card += '♥ ';
228 | } else if(color == CardType.MEIHUA) {
229 | card += '♣';
230 | } else if(color == CardType.FANGKUAI) {
231 | card += '♦ ';
232 | } else if(color == CardType.XIAOWANG) {
233 | if(card_val == 78) {
234 | card += 's';
235 | } else if(card_val == 79) {
236 | card += 'B ';
237 | }
238 | }
239 |
240 | if(card_val == 78) {
241 | card +='_'+CardVal.CARD_XIAOWANG;
242 | } else if(card_val == 79) {
243 | card +='_'+CardVal.CARD_DAWANG;
244 | } else {
245 | //牌值渲染
246 | var value = parseInt(card_val % 16);
247 | switch(value) {
248 | case 1:
249 | card +='_'+CardVal.CARD_SAN;
250 | break;
251 | case 2:
252 | card +='_'+CardVal.CARD_SI;
253 | break;
254 | case 3:
255 | card +='_'+CardVal.CARD_WU;
256 | break;
257 | case 4:
258 | card +='_'+CardVal.CARD_LIU;
259 | break;
260 | case 5:
261 | card +='_'+CardVal.CARD_QI;
262 | break;
263 | case 6:
264 | card +='_'+CardVal.CARD_BA;
265 | break;
266 | case 7:
267 | card +='_'+CardVal.CARD_JIU;
268 | break;
269 | case 8:
270 | card +='_'+CardVal.CARD_SHI;
271 | break;
272 | case 9:
273 | card +='_'+CardVal.CARD_J;
274 | break;
275 | case 10:
276 | card +='_'+CardVal.CARD_Q;
277 | break;
278 | case 11:
279 | card +='_'+CardVal.CARD_K;
280 | break;
281 | case 12:
282 | card +='_'+CardVal.CARD_A;
283 | break;
284 | case 13:
285 | card +='_'+CardVal.CARD_ER;
286 | break;
287 | }
288 | }
289 | return card;
290 | },
291 |
292 | //日志显示协议返回数据
293 | log: function(data) {
294 | //document.getElementById('msgText').innerHTML += JSON.stringify(data) + '\n';
295 | console.log(data);
296 | },
297 |
298 | //显示提示语句
299 | showTips: function(tips) {
300 | document.getElementById('msgText').innerHTML += tips + '\n';
301 | }
302 | }
--------------------------------------------------------------------------------
/public/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Testing swoole-h5game
6 |
7 |
8 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
91 |
--------------------------------------------------------------------------------
/resource/views/game/game.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 这是一个斗地主打牌测试工具
6 |
7 |
28 |
29 |
30 |
昵称:
31 |
32 | 服务器链接:
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 我的手牌:
46 |
51 |
52 |
53 |
54 |
55 | 上次出牌:
56 |
57 |
58 |
59 |
60 |
61 | 本次出牌:
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | 发送消息:
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
153 |
154 |
--------------------------------------------------------------------------------
/resource/views/game/login.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | login
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
31 |
32 |
33 |
34 |
35 |
36 | 用户帐号:
37 |
38 |
39 |
40 |
41 | =$tips?>
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/resource/views/game/test.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Testing swoole-h5game
6 |
7 |
8 |
9 | swoole-h5game Test工具
10 |
11 |
12 | 服务器:
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 昵称:
25 |
26 |
27 |
28 |
29 |
30 |
31 | 请求类型:
32 | ChatMsg
33 | GameStart
34 | GameCall
35 |
36 |
37 | 请求数据:
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
87 |
88 |
--------------------------------------------------------------------------------
/resource/views/vedio/camera.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 录像页面
5 |
6 |
7 |
8 |
9 |
10 |
11 | 提示:最好用火狐测试,谷歌浏览器升级了安全策略,谷歌浏览器只能在https下才能利用html5打开摄像头。
12 |
13 |
106 |
107 |
--------------------------------------------------------------------------------
/resource/views/vedio/show.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 录像显示页面
5 |
6 |
7 |
8 |
9 |
10 | 如果显示空白,说明当前没有人在直播,点击这里直播
11 |
77 |
78 |
--------------------------------------------------------------------------------
/test/ai.php:
--------------------------------------------------------------------------------
1 | true,
24 | );
25 |
26 | /**
27 | * 客户端头部设置
28 | * @var array
29 | */
30 | private $_header = array(
31 | 'UserAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
32 | );
33 |
34 | /**
35 | * 用户登陆账号
36 | * @var string
37 | */
38 | public $account = '';
39 |
40 | /**
41 | * 心跳定时器
42 | * @var int
43 | */
44 | public $heart_timer = 0;
45 |
46 | /**
47 | * 心跳定时器间隔时间(毫秒)
48 | * @var int
49 | */
50 | public $heart_interval = 60000;
51 |
52 | /**
53 | * 断线重连定时器
54 | * @var int
55 | */
56 | public $reback_timer = 0;
57 |
58 | /**
59 | * 断线重连次数
60 | * @var int
61 | */
62 | public $reback_times = 10;
63 |
64 | /**
65 | * 断线重连计数器
66 | * @var int
67 | */
68 | public $reback_count = 0;
69 |
70 | /**
71 | * 断线重连间隔时间(毫秒)
72 | * @var int
73 | */
74 | public $reback_interval = 2000;
75 |
76 | /**
77 | * 椅子id
78 | * @var array
79 | */
80 | public $chair_id = 0;
81 |
82 | /**
83 | * 手牌数据
84 | * @var array
85 | */
86 | public $hand_card = array();
87 |
88 | /**
89 | * 房间信息
90 | * @var array
91 | */
92 | public $my_room_info = array();
93 |
94 | /**
95 | * 手牌对象
96 | * @var null
97 | */
98 | public $ddz = null;
99 |
100 | /**
101 | * 路由规则
102 | * @var array
103 | */
104 | public $route = array(
105 | //系统请求响应
106 | App\Game\Conf\MainCmd::CMD_SYS => array(
107 | \App\Game\Conf\SubCmd::LOGIN_FAIL_RESP =>'loginFailResp', //登录失败响应
108 | \App\Game\Conf\SubCmd::LOGIN_SUCCESS_RESP =>'loginSucessResp', //登录成功响应
109 | \App\Game\Conf\SubCmd::HEART_ASK_RESP =>'heartAskResp', //心跳响应
110 | \App\Game\Conf\SubCmd::ENTER_ROOM_FAIL_RESP =>'enterRoomFailResp', //进入房间失败响应
111 | \App\Game\Conf\SubCmd::ENTER_ROOM_SUCC_RESP =>'enterRoomSuccResp', //进入房间成功响应
112 | ),
113 | //游戏请求响应
114 | App\Game\Conf\MainCmd::CMD_GAME => array(
115 | \App\Game\Conf\SubCmd::SUB_GAME_START_RESP =>'gameStartResp', //游戏开始响应
116 | \App\Game\Conf\SubCmd::SUB_USER_INFO_RESP =>'userInfoResp', //用户信息响应
117 | \App\Game\Conf\SubCmd::CHAT_MSG_RESP =>'chatMsgResp', //聊天,消息响应
118 | \App\Game\Conf\SubCmd::SUB_GAME_CALL_TIPS_RESP =>'gameCallTipsResp', //叫地主广播响应
119 | \App\Game\Conf\SubCmd::SUB_GAME_CALL_RESP =>'gameCallResp', //叫地主响应
120 | \App\Game\Conf\SubCmd::SUB_GAME_CATCH_BASECARD_RESP =>'catchGameCardResp', //摸牌广播响应
121 | \App\Game\Conf\SubCmd::SUB_GAME_OUT_CARD =>'gameOutCard', //出牌广播
122 | \App\Game\Conf\SubCmd::SUB_GAME_OUT_CARD_RESP =>'gameOutCardResp', //出牌响应
123 | ),
124 | );
125 |
126 | /**
127 | * 构造函数
128 | * Ai constructor.
129 | * @param string $account
130 | */
131 | public function __construct($account = ''){
132 | if($account) {
133 | $this->account = $account;
134 | }
135 | }
136 |
137 | /**
138 | * 运行服务器
139 | */
140 | public function run(){
141 | if($this->account) {
142 | $this->createConnection();
143 | } else {
144 | \App\Game\Core\Log::show("账号错误!");
145 | }
146 | }
147 |
148 | /**
149 | * 创建链接
150 | */
151 | protected function createConnection() {
152 | go(function () {
153 | $cli = new \Swoole\Coroutine\Http\Client(self::IP, self::PORT);
154 | $cli->set($this->_setconfig);
155 | $cli->setHeaders($this->_header);
156 | $cli->setMethod("GET");
157 | $self = $this;
158 | $data = array('account' => $this->account);
159 | $token = json_encode($data);
160 | $ret = $cli->upgrade('/game?token=' . $token);
161 | if($ret && $cli->connected) {
162 | //清除断线重连定时器, 断线重连次数重置为0
163 | Swoole\Timer::clear($this->reback_timer);
164 | $this->reback_count = 0;
165 | // $self->chatMsgReq($cli); //测试聊天请求
166 | $self->heartAskReq($cli); //发送心跳
167 | while (true) {
168 | $ret = $self::onMessage($cli, $cli->recv());
169 | if(!$ret) {
170 | break;
171 | }
172 | }
173 |
174 | }
175 | });
176 | }
177 |
178 | /**
179 | * websocket 消息处理
180 | * @param $cli
181 | * @param $frame
182 | * @return bool
183 | */
184 | public function onMessage($cli, $frame) {
185 | \App\Game\Core\Log::show('原数据:'.$frame->data);
186 | $ret = false;
187 | if($cli->connected && $frame) {
188 | $total_data = $frame->data;
189 | $total_len = strlen($total_data);
190 | if ($total_len < 4) {
191 | //清除定时器
192 | Swoole\Timer::clear($this->timer);
193 | //断开链接
194 | $cli->close();
195 | \App\Game\Core\Log::show('数据包格式有误!');
196 | } else {
197 | //需要进行粘包处理
198 | $off = 0; //结束时指针
199 | while ($total_len > $off) {
200 | $header = substr($total_data, $off, 4);
201 | $arr = unpack("Nlen", $header);
202 | $len = isset($arr['len']) ? $arr['len'] : 0;
203 | if ($len) {
204 | $data = substr($total_data, $off, $off + $len + 4);
205 | $body = \App\Game\Core\Packet::packDecode($data);
206 | $this->dispatch($cli, $body);
207 | $off += $len + 4;
208 | } else {
209 | break;
210 | }
211 | }
212 | }
213 | $ret = true;
214 | } else {
215 | //清除定时器
216 | Swoole\Timer::clear($this->heart_timer);
217 | Swoole\Timer::clear($this->reback_timer);
218 | //链接断开, 可以尝试断线重连逻辑
219 | $cli->close();
220 | \App\Game\Core\Log::show('链接断开: 清除定时器, 断开链接!');
221 | //断线重连逻辑
222 | $this->rebackConnection();
223 | }
224 | return $ret;
225 | }
226 |
227 | /**
228 | * 断线重连
229 | */
230 | protected function rebackConnection() {
231 | \App\Game\Core\Log::show('断线重连开始');
232 | //定时器发送数据,发送心跳数据
233 | $this->reback_timer = Swoole\Timer::tick($this->reback_interval, function () {
234 | if($this->reback_count < $this->reback_times) {
235 | $this->reback_count++;
236 | $this->createConnection();
237 | \App\Game\Core\Log::show('断线重连' . $this->reback_count . '次');
238 | } else {
239 | Swoole\Timer::clear($this->reback_timer);
240 | Swoole\Timer::clear($this->heart_timer);
241 | }
242 | });
243 | }
244 |
245 | /**
246 | * 转发到不同的逻辑处理
247 | * @param $cli
248 | * @param $cmd
249 | * @param $scmd
250 | * @param $data
251 | */
252 | protected function dispatch($cli, $data) {
253 | $cmd = isset($data['cmd']) ? intval($data['cmd']) : 0;
254 | $scmd = isset($data['scmd']) ? intval($data['scmd']) : 0;
255 | $len = isset($data['len']) ? intval($data['len']) : 0;
256 | $method = isset($this->route[$cmd][$scmd]) ? $this->route[$cmd][$scmd] : '';
257 | if($method) {
258 | if($method != 'heartAskResp') {
259 | \App\Game\Core\Log::show('----------------------------------------------------------------------------------------------');
260 | \App\Game\Core\Log::show('cmd = ' . $cmd . ' scmd =' . $scmd . ' len=' . $len . ' method=' . $method);
261 | }
262 | $this->$method($cli, $data['data']['data']);
263 | } else {
264 | \App\Game\Core\Log::show('cmd = '.$cmd . ' scmd =' .$scmd .' ,method is not exists');
265 | }
266 | }
267 |
268 | /**
269 | * 聊天请求
270 | * @param $cli
271 | */
272 | protected function chatMsgReq($cli) {
273 | if($cli->connected) {
274 | $msg = array('data' => 'this is a test msg');
275 | $data = \App\Game\Core\Packet::packEncode($msg, \App\Game\Conf\MainCmd::CMD_GAME, \App\Game\Conf\SubCmd::CHAT_MSG_REQ);
276 | $cli->push($data, WEBSOCKET_OPCODE_BINARY);
277 | }
278 | }
279 |
280 | /**
281 | * 触发心跳
282 | * @param $cli
283 | */
284 | protected function heartAskReq($cli) {
285 | //定时器发送数据,发送心跳数据
286 | $this->heart_timer = Swoole\Timer::tick($this->heart_interval, function () use ($cli) {
287 | list($t1, $t2) = explode(' ', microtime());
288 | $time = (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
289 | $msg = array('time' => $time);
290 | $data = \App\Game\Core\Packet::packEncode($msg, \App\Game\Conf\MainCmd::CMD_SYS, \App\Game\Conf\SubCmd::HEART_ASK_REQ);
291 | $ret = $cli->push($data, WEBSOCKET_OPCODE_BINARY);
292 | if(!$ret) {
293 | $this->loginFail($cli);
294 | }
295 | });
296 | }
297 |
298 | /**
299 | * 触发游戏开始
300 | * @param $cli
301 | */
302 | protected function gameStartReq($cli) {
303 | if($cli->connected) {
304 | $msg = array('data'=>'game start');
305 | $data = \App\Game\Core\Packet::packEncode($msg, \App\Game\Conf\MainCmd::CMD_GAME, \App\Game\Conf\SubCmd::SUB_GAME_START_REQ);
306 | $cli->push($data, WEBSOCKET_OPCODE_BINARY);
307 | }
308 | }
309 |
310 | /**
311 | * 发送叫地主请求
312 | * @param $cli
313 | * @param int $status 0表示不叫地主, 1表示叫地主
314 | */
315 | protected function gameCallReq($cli, $status = 0) {
316 | if($cli->connected) {
317 | $data = array('type'=>$status);
318 | $data = \App\Game\Core\Packet::packEncode($data, \App\Game\Conf\MainCmd::CMD_GAME, \App\Game\Conf\SubCmd::SUB_GAME_CALL_REQ);
319 | $cli->push($data, WEBSOCKET_OPCODE_BINARY);
320 | }
321 | }
322 |
323 | /**
324 | * 出牌请求
325 | * @param $cli
326 | * @param bool $is_first_round 是否为首轮, 首轮必须出牌
327 | */
328 | protected function outCardReq($cli, $is_first_round = false) {
329 | if($cli->connected) {
330 | \App\Game\Core\Log::show("开始出牌:");
331 | if($is_first_round) {
332 | $status = 1;
333 | $card = array(array_shift($this->hand_card)); //第一张牌, 打出去
334 | } else {
335 | //跟牌默认过牌, TODO:需要实现跟牌逻辑, 需要从自己手牌中找出打过上次牌的牌, 根据情况决定是否跟牌
336 | $status = 0; //出牌状态随机
337 | $card = array();
338 | }
339 | $msg = array(
340 | 'status' => $status, //打牌还是过牌, 1跟牌, 0是过牌
341 | 'chair_id' => $this->chair_id,
342 | 'card' => $card,
343 | );
344 | $data = \App\Game\Core\Packet::packEncode($msg, \App\Game\Conf\MainCmd::CMD_GAME, \App\Game\Conf\SubCmd::SUB_GAME_OUT_CARD_REQ);
345 | $cli->push($data, WEBSOCKET_OPCODE_BINARY);
346 | }
347 | }
348 |
349 |
350 |
351 | /**
352 | * 响应登录失败
353 | * @param $cli
354 | */
355 | protected function loginFailResp($cli, $data) {
356 | $cli->close();
357 | Swoole\Timer::clear($this->heart_timer);
358 | Swoole\Timer::clear($this->reback_timer);
359 | \App\Game\Core\Log::show("关闭客户端, 清除定时器");
360 | }
361 |
362 | /**
363 | * 响应登录成功
364 | * @param $cli
365 | */
366 | protected function loginSucessResp($cli, $data) {
367 | //登录成功, 开始游戏逻辑
368 | \App\Game\Core\Log::show("登录成功, 开始游戏请求");
369 | $this->gameStartReq($cli);
370 | }
371 |
372 | /**
373 | * 响应处理心跳
374 | * @param $cli
375 | */
376 | protected function heartAskResp($cli, $data) {
377 | //定时器发送数据,发送心跳数据
378 | \App\Game\Core\Log::show('心跳(毫秒):' .$data['time']);
379 | }
380 |
381 | /**
382 | * 响应处理聊天
383 | * @param $cli
384 | */
385 | protected function chatMsgResp($cli, $data) {
386 | //定时器发送数据,发送心跳数据
387 | \App\Game\Core\Log::show('聊天内容:'.json_encode($data));
388 | }
389 |
390 | /**
391 | * 触发游戏开始
392 | * @param $cli
393 | */
394 | protected function gameStartResp($cli, $data) {
395 | \App\Game\Core\Log::show('游戏场景数据:'.json_encode($data));
396 | }
397 |
398 | /**
399 | * 解说用户信息协议
400 | * @param $cli
401 | */
402 | protected function userInfoResp($cli, $data) {
403 | \App\Game\Core\Log::show('用户数据数据:'.json_encode($data));
404 | }
405 |
406 | /**
407 | * 进入房间后, 开始抢地主
408 | * @param $cli
409 | */
410 | protected function enterRoomSuccResp($cli, $data) {
411 | if($data['is_game_over']) {
412 | \App\Game\Core\Log::show('游戏结束');
413 | //触发开始游戏
414 | $this->gameStartReq($cli);
415 | } else {
416 | \App\Game\Core\Log::show('进入房间成功数据:'.json_encode($data));
417 | //保存用户信息和手牌信息
418 | $this->chair_id = $data['chair_id'];
419 | $this->hand_card = $data['card'];
420 | $this->my_room_info = $data;
421 | //如果没有叫地主, 触发叫地主动作
422 | if(!isset($data['calltype'])) {
423 | //根据自己的牌是否可以发送是否叫地主, 0,不叫, 1,叫地主, 2, 抢地主
424 | $obj = new \App\Game\Core\DdzPoker();
425 | $ret = $obj->isGoodCard($this->hand_card);
426 | $status = $ret ? 1 : 0;
427 | //发送是否叫地主操作
428 | $this->gameCallReq($cli, $status);
429 | }
430 | //是否轮到自己出牌, 如果是, 请出牌
431 | if(isset($data['index_chair_id']) && $data['index_chair_id'] == $this->chair_id) {
432 | if (isset($data['is_first_round']) && $data['is_first_round']) {
433 | //首轮出牌
434 | \App\Game\Core\Log::show('请出牌');
435 | } else {
436 | //跟牌操作
437 | \App\Game\Core\Log::show('请跟牌');
438 | }
439 | $this->outCardReq($cli, $data['is_first_round']);
440 | }
441 | }
442 | }
443 |
444 | /**
445 | * 自己叫完地主提示响应
446 | * @param $cli
447 | */
448 | protected function gameCallResp($cli, $data) {
449 | \App\Game\Core\Log::show('叫地主成功提示:'.json_encode($data));
450 | }
451 |
452 | /**
453 | * 叫完地主广播提示
454 | * @param $cli
455 | */
456 | protected function gameCallTipsResp($cli, $data) {
457 | $tips = $data['calltype'] ? $data['account'].'叫地主' : $data['account'].'不叫';
458 | \App\Game\Core\Log::show('广播叫地主提示:'.$tips);
459 | }
460 |
461 | /**
462 | * 触发游戏开始
463 | * @param $cli
464 | */
465 | protected function catchGameCardResp($cli, $data) {
466 | $tips = $data['user'].'摸底牌'.$data['hand_card'];
467 | \App\Game\Core\Log::show('摸底牌广播:'.$tips);
468 | if(isset($data['chair_id']) && $data['chair_id'] == $this->chair_id) {
469 | //合并手牌
470 | $hand_card = json_decode($data['hand_card'], true);
471 | $this->hand_card = $this->getDdzObj()->_sortCardByGrade(array_merge($this->hand_card, $hand_card));
472 | \App\Game\Core\Log::show('地主['.$this->account.']出牌:'.json_encode($this->hand_card));
473 | //地主首次出牌
474 | $this->outCardReq($cli, true);
475 | }
476 | }
477 |
478 | /**
479 | * 出牌提示
480 | * @param $cli
481 | */
482 | protected function gameOutCard($cli, $data) {
483 | \App\Game\Core\Log::show('出牌提示:'.json_encode($data));
484 | //移除手牌
485 | if(isset($data['status']) == 0 && isset($data['data']['card'])) {
486 | $this->hand_card = array_unique(array_values(array_diff($this->hand_card, $data['data']['card'])));
487 | }
488 | }
489 |
490 | /**
491 | * 出牌广播
492 | * @param $cli
493 | * @param $data
494 | */
495 | protected function gameOutCardResp($cli, $data) {
496 | \App\Game\Core\Log::show('出牌广播提示:'.json_encode($data));
497 | if(isset($data['is_game_over']) && $data['is_game_over']) {
498 | $tips = '广播:游戏结束,'.$data['account'].'胜利, 请点击"开始游戏",进行下一轮游戏';
499 | \App\Game\Core\Log::show($tips);
500 | //触发开始游戏
501 | $this->gameStartReq($cli);
502 | } else {
503 | $play = (isset($data['show_type']) && $data['show_type'] == 1) ? '跟牌': '过牌';
504 | $play = (isset($data['last_card']) && empty($data['last_card'])) ? '出牌' : $play;
505 | $last_card = !empty($data['last_card']) ? json_encode($data['last_card']) : '无';
506 | $out_card = !empty($data['card']) ? json_encode($data['card']) : '无';
507 | $tips = '广播: 第'.$data['round'].'回合,第'.$data['hand_num'].'手出牌, '.$data['account'].$play.', 上次牌值是'.$last_card.', 本次出牌值是'.$out_card .', 本次出牌牌型'.$data['card_type'];
508 | \App\Game\Core\Log::show($tips);
509 | //下次出牌是否轮到自己, 轮到自己, 请出牌
510 | if(isset($data['next_chair_id']) && $data['next_chair_id'] == $this->chair_id) {
511 | //出牌请求, 默认过牌操作
512 | if (isset($data['is_first_round']) && $data['is_first_round']) {
513 | //首轮出牌
514 | \App\Game\Core\Log::show('请出牌');
515 | //地主首次出牌
516 | } else {
517 | //跟牌操作
518 | \App\Game\Core\Log::show('请跟牌');
519 | //地主首次出牌
520 | }
521 | $this->outCardReq($cli, $data['is_first_round']);
522 | }
523 | }
524 | }
525 |
526 | /**
527 | * 说有没处理的方法, 输出
528 | * @param $name
529 | * @param $arguments
530 | */
531 | public function __call($name, $arguments)
532 | {
533 | \App\Game\Core\Log::show($name.':'.json_encode($arguments[1]));
534 | }
535 |
536 | /**
537 | * 获取手牌对象
538 | */
539 | public function getDdzObj() {
540 | if($this->ddz === null) {
541 | $this->ddz = new \App\Game\Core\DdzPoker();
542 | }
543 | return $this->ddz;
544 | }
545 | }
546 |
547 | $ai = new Ai($argv[1]);
548 | $ai->run();
549 |
--------------------------------------------------------------------------------
/test/tcp_client.php:
--------------------------------------------------------------------------------
1 | $code,
14 | "msg" => $msg,
15 | "data" => $data,
16 | );
17 | return $pack;
18 | }
19 |
20 | /**
21 | * 打包数据,固定包头,4个字节为包头(里面存了包体长度),包体前2个字节为
22 | */
23 | public static function packEncode($data, $cmd = 1, $scmd = 1, $format='msgpack', $type = "tcp") {
24 | if ($type == "tcp") {
25 | if($format == 'msgpack') {
26 | $sendStr = msgpack_pack($data);
27 | } else {
28 | $sendStr = $data;
29 | }
30 | $sendStr = pack('N', strlen($sendStr) + 2) . pack("C2", $cmd, $scmd). $sendStr;
31 | return $sendStr;
32 | } else {
33 | return self::packFormat("packet type wrong", 100006);
34 | }
35 | }
36 |
37 | /**
38 | * 解包数据
39 | */
40 | public static function packDecode($str, $format='msgpack') {
41 | $header = substr($str, 0, 4);
42 | if(strlen($header) != 4) {
43 | return self::packFormat("packet length invalid", 100007);
44 | } else {
45 | $len = unpack("Nlen", $header);
46 | $len = $len["len"];
47 | $cmd = unpack("Ccmd/Cscmd", substr($str, 4, 6));
48 | $result = substr($str, 6);
49 | if ($len != strlen($result) + 2) {
50 | //结果长度不对
51 | return self::packFormat("packet length invalid", 100007);
52 | }
53 |
54 | if($format == 'msgpack') {
55 | $result = msgpack_unpack($result);
56 | }
57 |
58 | if(empty($result)) {
59 | //结果长度不对
60 | return self::packFormat("packet data is empty", 100008);
61 | }
62 |
63 | // $result = self::packFormat("OK", 0, $result);
64 | $result['cmd'] = $cmd['cmd'];
65 | $result['scmd'] = $cmd['scmd'];
66 | $result['len'] = $len + 4;
67 | return $result;
68 | }
69 | }
70 | }
71 |
72 |
73 | //测试发送protobuf 发送请求
74 | $client = new swoole_client(SWOOLE_SOCK_TCP);
75 | if (!$client->connect('127.0.0.1', 18309, -1))
76 | {
77 | exit("connect failed. Error: {$client->errCode}\n");
78 | }
79 | $data = 'this is a system msg';
80 | $back = Packet::packEncode($data, 2, 213);
81 | $client->send($back);
82 | $res = $client->recv();
83 | echo '返回加密数据:'.$res."\n";
84 | //解开数据
85 | $res = Packet::packDecode($res);
86 | echo "解开返回数据\n";
87 | print_r($res);
88 | $client->close();
89 |
--------------------------------------------------------------------------------
/test/test_redis.php:
--------------------------------------------------------------------------------
1 | connect('192.168.1.155', 6379);
12 | $redis->setOptions(['compatibility_mode' => true]);
13 | $room_no_key = "user:room:no";
14 | if($redis->exists($room_no_key)) {
15 | echo '+++++++++++++++++++++++++++++++++++';
16 | $room_no = $redis->incr($room_no_key);
17 | echo $room_no;
18 | } else {
19 | $room_no = 1000001;
20 | $redis->set($room_no_key, $room_no);
21 | }
22 | var_dump($room_no);
23 | });
--------------------------------------------------------------------------------
/test/test_rpc.php:
--------------------------------------------------------------------------------
1 | '2.0',
24 | "method" => sprintf("%s::%s::%s", $version, $class, $method),
25 | 'params' => $param,
26 | 'id' => '',
27 | 'ext' => $ext,
28 | ];
29 | $data = json_encode($req) . RPC_EOL;
30 | fwrite($fp, $data);
31 |
32 | $result = '';
33 | while (!feof($fp)) {
34 | $tmp = stream_socket_recvfrom($fp, 1024);
35 |
36 | if ($pos = strpos($tmp, RPC_EOL)) {
37 | $result .= substr($tmp, 0, $pos);
38 | break;
39 | } else {
40 | $result .= $tmp;
41 | }
42 | }
43 |
44 | fclose($fp);
45 | return json_decode($result, true);
46 | }
47 |
48 | $ret = request('tcp://127.0.0.1:18307', \App\Rpc\Lib\UserInterface::class, 'getList', [1, 2], "1.0");
49 | var_dump($ret);
50 |
51 | $uid = 10001;
52 | $url = 'tcp://127.0.0.1:18307';
53 | $result = request($url, \App\Rpc\Lib\DbproxyInterface::class, 'execProc', ['accounts_mj', 'sp_account_get_by_uid', [$uid]], "1.0");
54 | var_dump($result);
55 |
56 |
57 | //$result = call('App\Lib\DbproxyInterface', '1.0.0', 'execProc', ['accounts_mj', 'sp_account_get_by_uid', [$uid]]);
58 | //$result1 = call('App\Lib\DemoInterface', '1.0.1', 'getUsers', [[$uid]]);
59 | //$result2 = call('App\Lib\DbproxyInterface', '1.0.0', 'execProc', ['activity_mj', 'sp_winlist_s', [1,10032,'',0]]);
60 | //
61 | //
62 | //$result3 = call('App\Lib\DbproxyInterface', '1.0.0', 'getYuanbao', [$uid]);
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------