ChatGPT を使用して、WebSocket を介してインタラクティブなバックギャモン WeChat アプレットを開発する (2)

1 はじめに

1.1 実施の原則

一般的な方向性は、ChatGPT をバックエンド言語モデルとして使用し、それをフロントエンドのグラフィカル ユーザー インターフェイス (GUI) と統合することです。以下に 3 つの重要な要素を示します。

  • ゲームロジック

まず、ゲーム ロジックの観点から、检测玩家点击的位置対応するピースをボードに配置する必要があります。各プレイヤーがプレイした後、ゲームが終了したかどうか、どちらが勝ったかを確認する必要があります。これは で実現できます遍历棋盘上的棋子それぞれのチェス駒の周りに、8方向に同じチェス駒が5つあるかどうかをチェックし、そうであればゲームは終了し、勝者が発表されます。

  • ChatGPTとの統合

プレイヤーが ChatGPT と対話できるようにするために、各プレイヤーがチェスをプレイした後に、チェス盤の状態を入力として ChatGPT モデルに渡すことができます。ChatGPT は、次のステップがあるべきだと ChatGPT が考える場所を示す文字列を出力します。次に、ChatGPT のチェスの駒として、チェス盤上のこの位置を別の色で表示します。

  • インターフェース

最初に、ゲーム インターフェイスを作成し、ウィンドウを 15x15 のグリッドに分割し、その中にボード ラインを描画します。

最後に、プレイヤーがゲームを操作できるようにするユーザー インターフェイスを作成する必要があります。ウィンドウの下部に、ChatGPT のテキスト ヒントを入力するためのテキスト ボックスと、ChatGPT がチェスをプレイするためのボタンを追加できます。プレイヤーはボード上の位置をクリックしてボタンを押すと、ChatGPT の入力として使用できます。ChatGPT からチェスのプロンプトを受け取った後、プログラムは自動的にチェスの駒をボードに配置します。

1.2 WeChat ミニ プログラムへの連絡方法

基本的なプロセスは次のとおりです。

  1. ユーザーは WeChat ミニプログラムに入り、バックギャモン ゲーム ページを開きます。
  2. ゲーム ページには、ボード、駒、ヒント情報などのゲーム インターフェイスが表示されます。
  3. ユーザーはボード上の位置をクリックして、次の動きとして使用します。
  4. ユーザーがチェス ボタンをクリックして、チェス イベントをトリガーします。
  5. アプレットは、ユーザーの位置と現在のボードの状態を ChatGPT モデルへの入力として渡します。
  6. ChatGPT モデルは、次のステップに最適な位置を計算し、その位置をアプレットに返します。
  7. アプレットは、ボード上の ChatGPT の位置を表示します。
  8. アプレットは、ゲームが終了したかどうかを確認し、勝者を宣言します。
  9. ユーザーは、ゲームをもう一度プレイするか、ゲームを終了するかを選択できます。

文字で簡単なフローチャートを描いて、おばあちゃんが読んでくれます。

+---------+       +--------+      +--------+
|         |       |        |      |        |
|进入小程序 | -->   |游戏界面 | -->  |  下棋   |
|         |       |        |      |        |
+---------+       +--------+      +--------+
                                       |
                                       |
                                       v
                                +-------------+
                                | ChatGPT 模型 |
                                +-------------+
                                       |
                                       |
                                       v
                                +------------+
                                | 在棋盘上下棋 |   
                                +------------+
                                      |
                                      |
                                      v
                               +--------------+
                               | 游戏结束或退出 |
                               +--------------+

2 バックギャモン アイテム

2.1 OpenAIのAPIを申し込む

相変わらず、OpenAI の API URL: https://platform.openai.com/account/api-keys

2.2 API コードの呼び出し

Python のリクエスト ライブラリを使用します。

import os
import requests
import json

API_KEY = os.environ.get("OPENAI_API_KEY")

def generate_text(prompt: str, max_tokens: int) -> str:
    headers = {
    
    
        "Content-Type": "application/json",
        "Authorization": f"Bearer {
      
      API_KEY}"
    }
    data = {
    
    
        "prompt": prompt,
        "max_tokens": max_tokens
    }
    response = requests.post(url="https://api.openai.com/v1/engines/davinci-codex/completions",
                             headers=headers,
                             data=json.dumps(data))
    response.raise_for_status()
    response_data = json.loads(response.text)
    generated_text = response_data["choices"][0]["text"]
    return generated_text.strip()

if __name__ == "__main__":
    prompt = "Hello, my name is"
    max_tokens = 5
    try:
        generated_text = generate_text(prompt, max_tokens)
        print(f"Generated text: {
      
      generated_text}")
    except requests.exceptions.RequestException as e:
        print(f"Error: {
      
      e}")

OpenAI ライブラリの場合:

from flask import Flask, request
import openai
import os

app = Flask(__name__)

openai.api_key = os.environ.get("OPENAI_API_KEY")

def generate_response(prompt: str, max_tokens: int = 60) -> str:
    response = openai.Completion.create(
        engine="davinci",
        prompt=prompt,
        max_tokens=max_tokens
    )
    return response.choices[0].text.strip()

@app.route("/")
def home():
    return "Hello, World!"

@app.route("/chat", methods=["POST"])
def chat():
    try:
        data = request.json
        message = data["message"]
        if not message:
            return "Invalid message", 400
        response = generate_response(message)
        return response
    except (KeyError, ValueError):
        return "Invalid request data", 400
    except Exception as e:
        return str(e), 500

if __name__ == "__main__":
    port = int(os.environ.get("PORT", 5000))
    app.run(host="0.0.0.0", port=port, debug=True)

2.3 インターフェースコード

 <template>
	<view class="chat-room">

		<gpt-card :show="showGPT"></gpt-card>
				
		
		<view class="box" >
			<view class="centent"><canvas @touchend="syncAction" canvas-id="canvas" class="canvas" style="width: 730rpx;height: 730rpx;"></canvas></view>
			<view>
				<view
					:class="value.class"
					:style="{ left: value.left, top: value.top, transform: value.transform, boxShadow: value.boxShadow }"
					v-for="(value, index) in game.h"
					:key="index"
				>
					{
    
    {
    
     value.text }}
				</view>
			</view>
			<view class="winner">
				<view class="state-chess Bchess"></view>
				<view class="chessName"></view>
			</view>
		</view>
		
		<user-card :show="showUser"></user-card>
	
	</view>
</template>

<script>
	import userCard from '@/components/infoCard/index.vue'
	import gptCard from '@/components/gptCard/index.vue'
	let goEasy = getApp().globalData.goEasy;
	let pubSub = goEasy.pubsub;
	export default {
    
    
		data() {
    
    
			return {
    
    
				showGPT: false,
				showUser: true,
				userInfo:{
    
    
					chessRole:1, // 1为白棋,2为黑棋
					roundFlag:true, // 表示是否为自己的回合
					enemy:'',
					name:''
				},
				chessMassage:{
    
    
					body:'',
					playerA:'',
					playerB:'',
					chessRole:1,
					mode:1
				},
				MoveMode:{
    
    
					a2b:1,
					b2a:2
				},
				game: {
    
    
				ctx: null,
				e: 0,
				chess_Board: [],
				chess_Name: ['黑棋', '白棋'],
				h: [],
				um: 0,
				lianz: [],
				winXY: [[1, 0], [0, 1], [1, 1], [1, -1]],
				chessOff: true
			},
			cName: '黑棋走',
			sChesee: 'Bchess',
				currentRoom: null,
				// 道具展示
				propDisplay: {
    
    
					showPropType: 0,
					play: false,
					timer: null
				},
				newMessageContent: "",
				// 道具类型
				Prop: {
    
    
					HEART: 0,//桃心
					ROCKET: 1//火箭
				},
				
				// 消息类型
				MessageType: {
    
    
					CHAT: 0,//文字聊天
					PROP: 1,//道具
					CHESS:2 // 下棋
				}
			}
		},
		components:{
    
    userCard, gptCard},
		onLoad(options) {
    
    
			//获取数据
			let roomToken = JSON.parse(options.roomToken);
			// 初始化room
			this.currentRoom = {
    
    
				roomId: roomToken.roomId,
				roomName: roomToken.roomName,
				onlineUsers: {
    
    
					count: 0,
					users: []
				},
				messages: [],
				currentUser: {
    
    
					id: roomToken.userId,
					nickname: roomToken.nickname,
					avatar: roomToken.avatar
				}
			};
			this.userInfo.name  = roomToken.nickname
			// 设置导航标题
			uni.setNavigationBarTitle({
    
    
				title: roomToken.roomName
			});

			// 连接goEasy
			this.connectGoEasy();

            // 监听用户上下线
            this.listenUsersOnlineOffline();


            // 加载最后10条消息历史
            this.loadHistory();

            // 监听新消息
            this.listenNewMessage();



        },
		onReady() {
    
    
			this.game.ctx = uni.createCanvasContext('canvas');
			this.drawLine();
		},
		onUnload() {
    
    
			// 断开连接
			goEasy.disconnect({
    
    
				onSuccess(){
    
    
					console.log("GoEasy disconnect successfully");
				},
				onFailed(error){
    
    
					console.log("GoEasy disconnect failed"+JSON.stringify(error));
				}
			});
		},
		methods: {
    
    
			// 连接goEasy
			connectGoEasy(){
    
    
				let self = this;
				let userData = {
    
    
					avatar: this.currentRoom.currentUser.avatar,
					nickname: this.currentRoom.currentUser.nickname
				}
				goEasy.connect({
    
    
					id : this.currentRoom.currentUser.id,
					data : userData,
					onSuccess: function(){
    
    
						console.log("GoEasy connect successfully.")

                        // 加载在线用户列表
                        self.loadOnlineUsers();
					},
					onFailed: function(error){
    
    
						console.log("Failed to connect GoEasy, code:"+error.code+ ",error:"+error.content);
					},
					onProgress: function(attempts){
    
    
						console.log("GoEasy is connecting", attempts);
					}
				});
			},
			// 监听用户上下线
			listenUsersOnlineOffline(){
    
    
				let self = this;
				let roomId = this.currentRoom.roomId;
				pubSub.subscribePresence({
    
    
					channel: roomId,
					onPresence: function (presenceEvents) {
    
    
						self.currentRoom.onlineUsers.count = presenceEvents.clientAmount;
						presenceEvents.events.forEach(function (event) {
    
    
							let userData = event.data;
							if (event.action === "join" || event.action === "online") {
    
    
								//进入房间
								let userId = event.id;
								let avatar = userData.avatar;
								let nickname = userData.nickname;
								let user = {
    
    
									id: userId,
									avatar: avatar,
									nickname: nickname
								};
								//添加新用户
								self.currentRoom.onlineUsers.users.push(user);
								//添加进入房间的消息
								let message = {
    
    
									content: " 进入房间",
									senderUserId: userId,
									senderNickname: nickname,
									type: self.MessageType.CHAT
								};
								self.currentRoom.messages.push(message);
							} else {
    
    
								//退出房间
								self.currentRoom.onlineUsers.users.forEach((user, index) => {
    
    
									if (event.id === user.id) {
    
    
										// 删除当前聊天室列表中离线的用户
										let offlineUser = self.currentRoom.onlineUsers.users.splice(index, 1);
										let message = {
    
    
											content: " 退出房间",
											senderUserId: offlineUser[0].id,
											senderNickname: offlineUser[0].nickname,
											type: self.MessageType.CHAT
										};
										self.currentRoom.messages.push(message);
									}
								});
							}
							self.scrollToBottom();
						});
					},
					onSuccess : function () {
    
    
						console.log("用户上下线监听成功")
					},
					onFailed : function (error) {
    
    
						console.log("监听用户上下线失败, code:"+error.code+ ",content:"+error.content);
                    }
				})
			},
			switchRound(){
    
    
				this.showGPT = !this.showGPT
				this.showUser = !this.showUser
			},
			// 监听新消息
			listenNewMessage(){
    
    
				// 监听当前聊天室的消息
				let self = this;
				let roomId = this.currentRoom.roomId;
				pubSub.subscribe({
    
    
					channel: roomId,
					onMessage : function (message) {
    
    
						let messageContent = "";
						let content = JSON.parse(message.content);
						//聊天消息
						if(content.type === self.MessageType.CHAT) {
    
    
							messageContent = content.content;
						}
						//道具消息
						if(content.type === self.MessageType.PROP) {
    
    
							if (content.content === self.Prop.ROCKET) {
    
    
								messageContent = "送出了一枚大火箭";
							}
							if (content.content === self.Prop.HEART) {
    
    
								messageContent = "送出了一个大大的比心";
							}
						}
						
						 console.log("监听消息成功==",content)
						if(content.type === self.MessageType.CHESS){
    
    
														
							self.canvasClick(content.body,content.chessRole)
							self.userInfo.roundFlag = true
							self.switchRound()
						}
						//添加消息
						let newMessage = {
    
    
							content: messageContent,
							senderUserId: content.senderUserId,
							senderNickname: content.senderNickname,
							type: self.MessageType.CHAT
						};
						self.currentRoom.messages.push(newMessage);
						if (content.type === self.MessageType.PROP) {
    
    
							self.propAnimation(parseInt(content.content))
						}
						self.scrollToBottom();
					},
					onSuccess : function () {
    
    
					  console.log("监听新消息成功")
					},
					onFailed : function(error) {
    
    
						console.log("订阅消息失败, code:"+error.code+ ",错误信息:"+error.content);
					}
				})
			},
			// 加载在线用户列表
			loadOnlineUsers(){
    
    
				let self = this;
				let roomId = this.currentRoom.roomId;
				pubSub.hereNow({
    
    
					channels : [roomId],
					includeUsers : true,
					distinct : true,
					onSuccess: function (result) {
    
    
						let users = [];
						let currentRoomOnlineUsers = result.content.channels[roomId];
						currentRoomOnlineUsers.users.forEach(function (onlineUser) {
    
    
							let userData = onlineUser.data;
							let user = {
    
    
								id: onlineUser.id,
								nickname: userData.nickname,
								avatar: userData.avatar
							};
							users.push(user);
						});
						self.currentRoom.onlineUsers = {
    
    
							users: users,
							count: currentRoomOnlineUsers.clientAmount
						};
						// 如果是第一个进房的就自动设为白棋
						// 如果是第二个进房的就是设为黑棋
						if(users.length==1){
    
    
							self.userInfo.chessRole = 1
							self.userInfo.name = users[0].nickname
						}
						if(users.length==2){
    
    
							self.userInfo.chessRole = 2
							self.userInfo.name = users[1].nickname
						}
						
					},
					onFailed: function (error) {
    
    
						//获取失败
                        console.log("获取在线用户失败, code:" + error.code + ",错误信息:" + error.content);
					}
				});
			},
			// 加载最后10条消息历史
			loadHistory(){
    
    
				let self = this;
				let roomId = this.currentRoom.roomId;
				pubSub.history({
    
    
					channel: roomId, //必需项
					limit: 10, //可选项,返回的消息条数
					onSuccess:function(response){
    
    
						let messages = [];
						response.content.messages.map(message => {
    
    
							let historyMessage = JSON.parse(message.content);
							//道具消息
							if (historyMessage.type === self.MessageType.PROP) {
    
    
								if (historyMessage.content === self.Prop.ROCKET) {
    
    
									historyMessage.content = "送出了一枚大火箭";
								}
								if (historyMessage.content === self.Prop.HEART) {
    
    
									historyMessage.content = "送出了一个大大的比心";
								}
							}
							messages.push(historyMessage);
						});
						self.currentRoom.messages = messages;
					},
					onFailed: function (error) {
    
    
                        console.log("获取历史消息失败, code:" + error.code + ",错误信息:" + error.content);
					}
				});
			},
			onInputMessage(event) {
    
    //双向绑定消息 兼容
				this.newMessageContent = event.target.value;
			},
			sendMessage(messageType, content) {
    
    
				//发送消息
				if (content === "" && messageType === this.MessageType.CHAT) {
    
    
					return;
				}
				var message = {
    
    
					senderNickname: this.currentRoom.currentUser.nickname,
					senderUserId: this.currentRoom.currentUser.id,
					type: messageType,
					content: content
				};
				
				if(messageType === this.MessageType.CHESS){
    
    
					this.chessMassage.body = content
					this.chessMassage.chessRole = this.userInfo.chessRole
					let userNum=this.currentRoom.onlineUsers.users.length
					
					message = {
    
    
						senderNickname: this.currentRoom.currentUser.nickname,
						senderUserId: this.currentRoom.currentUser.id,
						type: messageType,
						body:content,
						playerA:'',
						playerB:'',
						chessRole:this.userInfo.chessRole,
						mode:1,
						userNum:userNum
					}
				}
				console.log("发送==",message);
				pubSub.publish({
    
    
					channel : this.currentRoom.roomId,
					message : JSON.stringify(message),
					onSuccess : function () {
    
    
						console.log("发送成功");
					},
					onFailed : function (error) {
    
    
						console.log("消息发送失败,错误编码:" + error.code + " 错误信息:" + error.content);
					}
				});
				this.newMessageContent = "";
			},
			propAnimation(type) {
    
    //道具动画
				//动画的实现
				if (this.propDisplay.timer) {
    
    
					return;
				}
				this.propDisplay.showPropType = type;
				this.propDisplay.play = true;
				this.propDisplay.timer = setTimeout(() => {
    
    
					this.propDisplay.play = false;
					this.propDisplay.timer = null;
				}, 2000)
			},
			scrollToBottom () {
    
    
				this.$nextTick(function(){
    
    
					uni.pageScrollTo({
    
    
						scrollTop: 2000000,
						duration : 10
					})
				})
			},
			// ==== 五指棋控制逻辑  ===
			drawLine() {
    
    
				let s = uni.upx2px(730);
				let dis = Math.floor(s / 15);
				let w = dis * 14;
				for (let i = 1; i <= 14; i++) {
    
    
					this.game.ctx.moveTo(i * dis + 0.5, w);
					this.game.ctx.lineTo(i * dis + 0.5, dis);
					this.game.ctx.moveTo(dis, i * dis + 0.5);
					this.game.ctx.lineTo(w, i * dis + 0.5);
					this.game.ctx.setStrokeStyle('#a5aa6b');
					this.game.ctx.stroke();
				}
				this.game.ctx.draw();
				for (let i = 0; i <= 13; i++) {
    
    
					this.game.chess_Board[i] = [];
					this.game.lianz[i] = [];
					for (let j = 0; j <= 13; j++) {
    
    
						this.game.chess_Board[i][j] = 0;
						this.game.lianz[i][j] = 0;
					}
				}
			},
			syncAction(e){
    
    
				
				if(this.userInfo.roundFlag){
    
    
									
					
					this.sendMessage(this.MessageType.CHESS,e)
					this.canvasClick(e,this.userInfo.cheeRole)
					this.userInfo.roundFlag = false
				}else{
    
    
					uni.showModal({
    
    
						content: '还未到你的回合!'
					});
				}
				
			},
			canvasClick(e,chessRole) {
    
    
				console.log(JSON.stringify(e));
				let s = uni.upx2px(730);
				let dis = Math.floor(s / 15);
				let dx = parseInt(Math.floor(e.changedTouches[0].x + dis / 2) / dis);
				let dy = parseInt(Math.floor(e.changedTouches[0].y + dis / 2) / dis);
				let WBobj = {
    
    
					ox: dx * dis - dis / 2 + 10,
					oy: dy * dis - dis / 2 + 10,
					left: dx * dis - dis / 2 + 10 + 'px',
					top: dy * dis - dis / 2 + 10 + 'px',
					transform: '',
					boxShadow: '',
					text: '',
					mz: this.game.chess_Name[this.game.e % 2],
					class: this.game.e % 2 == 1 ? 'Wchess' : 'Bchess',
					list: this.game.um++
				};
				if (dx < 1 || (dx > dis - 1) | (dy < 1) || dy > dis - 1) return;
				if (this.game.chess_Board[dx - 1][dy - 1] == 0) {
    
    
					this.game.h.push(WBobj);
					this.game.chess_Board[dx - 1][dy - 1] = this.game.chess_Name[this.game.e % 2];
					this.game.lianz[dx - 1][dy - 1] = WBobj;
					this.win(dx - 1, dy - 1, this.game.chess_Name[this.game.e % 2], this.game.winXY[0], this.game.e % 2);
					this.win(dx - 1, dy - 1, this.game.chess_Name[this.game.e % 2], this.game.winXY[1], this.game.e % 2);
					this.win(dx - 1, dy - 1, this.game.chess_Name[this.game.e % 2], this.game.winXY[2], this.game.e % 2);
					this.win(dx - 1, dy - 1, this.game.chess_Name[this.game.e % 2], this.game.winXY[3], this.game.e % 2);
					this.cName = this.game.e % 2 == 0 ? this.game.chess_Name[1] + '走' : this.game.chess_Name[0] + '走';
					this.sChesee = chessRole==2? 'Bchess' : 'Wchess';
					this.game.e++;
				}
			},
			win(x, y, c, m, li) {
    
    
				let ms = 1;
				var continuity = [];
				for (let i = 1; i < 5; i++) {
    
    
					if (this.game.chess_Board[x + i * m[0]]) {
    
    
						if (this.game.chess_Board[x + i * m[0]][y + i * m[1]] === c) {
    
    
							continuity.push([x + i * m[0], y + i * m[1]]);
							ms++;
						} else {
    
    
							break;
						}
					}
				}
			
				for (let i = 1; i < 5; i++) {
    
    
					if (this.game.chess_Board[x - i * m[0]]) {
    
    
						if (this.game.chess_Board[x - i * m[0]][y - i * m[1]] === c) {
    
    
							continuity.push([x - i * m[0], y - i * m[1]]);
							ms++;
						} else {
    
    
							break;
						}
					}
				}
			
				if (ms >= 5) {
    
    
					setTimeout(function() {
    
    
						console.log(c + '赢了');
					}, 600);
					continuity.push([x, y]);
					this.game.chessOff = false;
					let s = 5;
					let ls = [270, 300, 330, 360, 390];
					let ls1 = [390, 420, 450, 480, 510];
					let _this = this;
					continuity.forEach(function(value, index) {
    
    
						let time = setInterval(function() {
    
    
							_this.game.lianz[value[0]][value[1]].transform = 'scale(0.9)';
							_this.game.lianz[value[0]][value[1]].boxShadow = '0px 0px 2px 2px #ffd507';
							s--;
							s <= 0 ? clearInterval(time) : clearInterval(time);
						}, ls[index]);
						let time2 = setInterval(function() {
    
    
							_this.game.lianz[value[0]][value[1]].transform = 'scale(1)';
							_this.game.lianz[value[0]][value[1]].boxShadow = '0px 0px 2px 2px #ffd507';
							s++;
							s >= 5 ? clearInterval(time2) : clearInterval(time2);
						}, ls1[index]);
					});
			
					for (var i = 0; i < this.game.chess_Board.length; i++) {
    
    
						for (var j = 0; j < this.game.chess_Board.length; j++) {
    
    
							if (this.game.chess_Board[i][j] === 0) {
    
    
								this.game.chess_Board[i][j] = 'null';
							}
						}
					}
			
					this.game.h.forEach(function(value, index) {
    
    
						value.text = value.list;
					});
			
					uni.showModal({
    
    
						content: c + '赢了'
					});
				}
			},
			regret() {
    
    
				if (this.game.chessOff) {
    
    
					if (this.game.h.length > 0) {
    
    
						let s = uni.upx2px(730);
						let dis = Math.floor(s / 15);
						let obj = this.game.h.pop();
						this.cName = this.game.e % 2 == 0 ? this.game.chess_Name[1] + '走' : this.game.chess_Name[0] + '走';
						this.sChesee = this.game.e % 2 == 1 ? 'Bchess' : 'Wchess';
						this.game.e -= 1;
						this.game.um -= 1;
						this.game.chess_Board[parseInt(obj.ox / dis)][parseInt(obj.oy / dis)] = 0;
					} else {
    
    
						return;
					}
				} else {
    
    
					return;
				}
			},
			anewClick() {
    
    
				this.game.h = [];
				this.game.um = 0;
				this.game.chessOff = true;
				for (let i = 0; i <= 13; i++) {
    
    
					this.game.chess_Board[i] = [];
					this.game.lianz[i] = [];
					for (let j = 0; j <= 13; j++) {
    
    
						this.game.chess_Board[i][j] = 0;
						this.game.lianz[i][j] = 0;
					}
				}
			}
		}
	}
</script>

<style>
	page {
    
    
		height: 100%;;
	}

	uni-page-body {
    
    
		height: 100%;;
	}

	.chat-room {
    
    
		display: flex;
		flex-direction: column;
		height: 100%;
	}

	.online-avatar-container {
    
    
		position: fixed;
		right: 0;
		width: 100%;
		height: 80rpx;
		display: flex;
		justify-content: flex-end;
		padding: 28rpx;
		box-shadow: 10rpx 30rpx 50rpx #fff;
		z-index: 40;
		background: #ffffff;
	}

	.online-avatar-item {
    
    
		width: 80rpx;
		height: 80rpx;
		border-radius: 40rpx;
		text-align: center;
		line-height: 80rpx;
		background: rgba(51, 51, 51, 0.3);
		color: #fff;
		font-size: 18rpx 28rpx;
	}

	.online-count {
    
    
		width: 80rpx;
		height: 80rpx;
		border-radius: 40rpx;
		text-align: center;
		line-height: 80rpx;
		background: rgba(51, 51, 51, 0.3);
		color: #fff;
		font-size: 28rpx;
	}

	.online-avatar-item image {
    
    
		width: 80rpx;
		height: 80rpx;
	}

	.chat-room-container {
    
    
		/* padding-top: 100rpx; */
	}

	.scroll-view {
    
    
		overflow-y: auto;
		padding: 20rpx 38rpx 130rpx 38rpx;
		box-sizing: border-box;
		-webkit-overflow-scrolling: touch;
	}

	.message-box {
    
    
		margin-top: 16rpx;
	}

	.message-item {
    
    
		box-sizing: border-box;
		height: 72rpx;
		background-color: rgba(196, 196, 196, 0.2);
		display: inline-block;
		font-size: 28rpx;
		border-radius: 100rpx;
		padding: 18rpx 30rpx;
		font-family: Microsoft YaHei UI;
	}

	.user-name {
    
    
		color: #D02129;
		font-family: Microsoft YaHei UI;
	}

	.user-message {
    
    
		color: #333;
		font-family: Microsoft YaHei UI;
	}

	.chat-room-input {
    
    
		position: fixed;
		bottom: 0;
		height: 92rpx;
		line-height: 92rpx;
		padding: 10rpx 28rpx 20rpx 28rpx;
		display: flex;
		background: #ffffff;
	}

	.uni-input {
    
    
		width: 528rpx;
		background-color: rgba(51, 51, 51, 0.1);
		height: 92rpx;
		border-radius: 100rpx;
		box-sizing: border-box;
		padding: 26rpx 40rpx;
		font-size: 28rpx;
	}

	.uni-btn {
    
    
		position: absolute;
		z-index: 1000;
		width: 72rpx;
		height: 72rpx;
		background: #D02129;
		right: 10rpx;
		top: 10rpx;
		border-radius: 72rpx;
		text-align: center;
		line-height: 72rpx;
		color: #fff;
		font-weight: bold;
		font-size: 32rpx;
	}

	.heart {
    
    
		width: 80rpx;
		height: 92rpx;
		padding: 0 15rpx;
	}

	.rocket {
    
    
		width: 40rpx;
		height: 92rpx;
	}

	.self {
    
    
		color: #D02129;
	}

	.show-animation {
    
    
		width: 80rpx;
		height: 320rpx;
		position: fixed;
		z-index: 44;
		left: 50%;
		bottom: 80rpx;
		margin: 0 -40rpx;
		justify-content: flex-end;
		animation: myanimation 2s linear;
	}

	.prop-heart {
    
    
		height: 80rpx;
		width: 80rpx;
	}

	.prop-rocket {
    
    
		height: 160rpx;
		width: 80rpx;
	}

	@keyframes myanimation {
    
    
		from {
    
    
			bottom: 80rpx;
		}
		to {
    
    
			bottom: 600rpx;
		}
	}






.box {
    
    
	position: relative;
	margin: 50rpx auto;
	width: 750rpx;
	height: 810rpx;
	background: #e6e7ec;
}

.centent {
    
    
	position: absolute;
	width: 730rpx;
	height: 730rpx;
	border: 1px solid #9e9e9e;
	overflow: hidden;
	border-radius: 8rpx;
	box-shadow: 0rpx 0rpx 5rpx 0rpx #9e9e9e;
	left: 10rpx;
	top: 20rpx;
}

.canvas {
    
    
	background: #f7e6b7;
}

.button,
.anew,
.state,
.winner {
    
    
	position: absolute;
	display: block;
	width: 100rpx;
	height: 55rpx;
	border-radius: 10rpx;
	outline: none;
	font-size: 22rpx;
	box-sizing: border-box;
	color: #00bcd4;
	background: #fff;
	border: none;
	box-shadow: 1rpx 1rpx 3rpx 1rpx #9e9e9e;
	top: 760rpx;
	left: 270rpx;
	user-select: none;
}
.anew {
    
    
	left: 150rpx;
}
.state {
    
    
	left: 400rpx;
	width: 140rpx;
}
.state .state-chess,
.winner .state-chess {
    
    
	position: absolute;
	width: 30rpx;
	height: 30rpx;
	top: 11rpx;
	left: 10rpx;
}

.state .chessName,
.winner .chessName {
    
    
	position: absolute;
	width: 80rpx;
	height: 30rpx;
	top: 12rpx;
	left: 45rpx;
	text-align: center;
	line-height: 30rpx;
	font-size: 15rpx;
}

.button:active,
.anew:active {
    
    
	transition-property: all;
	transition-duration: 1s;
	transition-timing-function: ease;
	transition-delay: 0s;
	transform: scale(0.8);
}

.Bchess {
    
    
	position: absolute;
	width: 40rpx;
	height: 40rpx;
	border-radius: 40rpx;
	background: radial-gradient(#9e9e9e -100%, #000000 100%);
	box-shadow: 1rpx 1rpx 2rpx 0rpx #000000;
	font-size: 10rpx;
	line-height: 50rpx;
	text-align: center;
	color: #fff;
}

.Wchess {
    
    
	position: absolute;
	width: 40rpx;
	height: 40rpx;
	border-radius: 40rpx;
	background: radial-gradient(#e4e4e4 10%, #b7aaaa);
	box-shadow: 1rpx 1rpx 2rpx 0rpx #0000006e;
	font-size: 10rpx;
	line-height: 50rpx;
	text-align: center;
	color: #000000;
}

.winner {
    
    
	width: 120rpx;
	left: 12rpx;
	display: none;
}
</style>


3 バックギャモンをフロントエンド アプレットに同期する

3.1 WebSocket の長い接続

簡単なWebSocket サービスはFlaskFlask-SocketIOで実装され、最初にライブラリをインストールします: 、socketio インスタンスを作成します:pip install flask-socketio

# app.py
from flask import Flask
from flask_socketio import SocketIO

socketio = SocketIO()

def create_app(config=None):
    app = Flask(__name__)
    app.config.from_mapping(
        SECRET_KEY='your-secret-key',
        # other Flask config parameters
    )
    if config is not None:
        app.config.from_mapping(config)

    socketio.init_app(app)
    register_blueprints(app)
    return app

def register_blueprints(app):
    from . import chat
    app.register_blueprint(chat.bp)

# chat.py
from flask import Blueprint, request
from flask_socketio import emit, join_room, leave_room

bp = Blueprint('chat', __name__)

@bp.route('/')
def index():
    return "Hello, World!"

@socketio.on('connect')
def on_connect():
    print('WebSocket连接已建立')

@socketio.on('disconnect')
def on_disconnect():
    print('WebSocket连接已断开')

@socketio.on('join')
def on_join(data):
    username = data['username']
    room = data['room']
    join_room(room)
    emit('message', f'{
      
      username} 加入了房间 {
      
      room}', room=room)

@socketio.on('leave')
def on_leave(data):
    username = data['username']
    room = data['room']
    leave_room(room)
    emit('message', f'{
      
      username} 离开了房间 {
      
      room}', room=room)

@socketio.on('message')
def on_message(data):
    username = data['username']
    message = data['message']
    room = data['room']
    emit('message', f'{
      
      username}: {
      
      message}', room=room)

3.2 リアルタイムのチェスを入手する

from flask import Flask, request, jsonify
from flask_socketio import SocketIO, emit
import openai
import os

# 初始化Flask应用和SocketIO
app = Flask(__name__)
socketio = SocketIO(app)

# 从环境变量中读取OpenAI API密钥
openai.api_key = os.getenv("OPENAI_API_KEY")

# 处理WebSocket连接事件
@socketio.on('connect')
def handle_connect():
    print('Client connected')

# 处理WebSocket断开连接事件
@socketio.on('disconnect')
def handle_disconnect():
    print('Client disconnected')

# 处理发送请求事件
@socketio.on('send_request')
def handle_request(request_text):
    try:
        # 调用OpenAI API获取回复
        response = openai.Completion.create(
            engine="davinci",
            prompt=request_text,
            max_tokens=50,
            n=1,
            stop=None,
            temperature=0.7
        )

        # 从API响应中提取回复文本
        response_text = response.choices[0].text.strip()

        # 将回复发送给前端
        emit('response', {
    
    'text': response_text})

    except Exception as e:
        # 如果发生异常,将错误信息返回给前端
        emit('response', {
    
    'error': str(e)})


if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', port=5000)

4。討議

バックギャモンを実現する動作原理を理解するのは簡単です. 主な難点はアプレットのインターフェイス コードです. チェス コンテンツのアプレットへの同期. このステップは視覚化として理解できます. 作者は OS システムです.パスか環境の問題と思われます. エラー報告は解消されておらず、上記のコードはWindowsとの組み合わせで最適化されており、完成品は次のパートで紹介されます!

おすすめ

転載: blog.csdn.net/weixin_48093827/article/details/130452730