一、写在前面
敢问世间万物,何以解忧?
时下最为火爆的ChatGPT想必够资格来回答一下这个问题。
要想当年AlphaGO打败世界围棋高手李世石,就展露出AI的惊人实力,时隔多年,AI领域在憋了这么多年之后,现如今,ChatGPT 4大杀四方,各行各业无不为之震撼!
借用刚召开的新程序员大会中的模型发展架构,我们可以很清晰的看到大模型现阶段的研究进展以及商业化现状。
在ChatGPT问世以来的这么几个月里,这种大模型在市场中的应用还是主要围绕在AI绘图``AI聊天以及一些AI文本类型的工作。就截至到目前,市面上对于大模型的实际生产应用仍然处在一个不断摸索的模糊地带。
回望2006-2009年的移动互联网的爆发年代,此时的跨时代的风口已经跃然纸上,要如何抓住机会逆天改命?毋庸置疑,贴近时代脉搏,疯狂尝试!
为此,我们来做一个酷炫的东西,我们做一个可以跟GPT下棋的小程序!一起来感受一下GPT的棋艺如何! 😁😪🙄😏😥😮
二、项目原理与架构
2.1方案原理
要让ChatGPT遵从我们所设计的表盘五指棋的规则,首先个一个要点就是要让其知道我们的棋盘的底层设计是如何的?
那这就要说回到五指棋棋盘的实现原理了:五指棋棋盘从代码实现的角度来说就是一个二维数组,用户在棋盘的每一步操作都是在这个二维数组中对相应的元素坐标中的值进行设置(如我们在棋盘的第一个位置下了一个棋子,然后这个棋盘数组元素赋值就会变成: arr [1][1] = 1 )
在明确了棋盘落子的基础之上,接下来还需要处理的就是让ChatGPT明白这个规则。通过借助ChatGPT所提供的API接口,
我们将数组的边界值以及用户所进行的落子操作传递给模型,然后再实时地将模型所返回的值进行渲染到前端即可。
API数据发送
用户
棋盘小程序
ChatGPT
数据处理
ChatPGT如何下棋?
要跟ChatGPT下棋,其实就是跟它进行对话,让他在五指棋的规则约定下跟他对话:
首先,我们先指定话题背景:我们现在开始采用传统的黑白棋子来下一盘五指棋,棋盘的大小是20*20。我是黑方,我将用坐标的形式来表示我下的位置。你需要采取进攻型的策略快速赢我,告诉我你的落子位置。
然后,我们只需要输入我们的落子坐标:如:(10,10)。
GPT就会根据五指棋的规则以及我们的落子坐标对棋盘进行分析,从而得出最佳的落子选择。
2.2 技术架构
(1)技术栈介绍
模块
语言及框架
涉及的技术要点
小程序前端
基于VUE 2.0语法+Uni-app跨平台开发框架
Http接口通信、Flex布局方式、uView样式库的使用、JSON数据解析、定时器的使用
小程序接口服务端
Python + Flask WEB框架
rest接口的开发、 ChatGPT API接口的数据对接 、 前后端websocket实时通信
(2)数据流转解析
从系统中的数据流向梳理整体的功能开发流程,进而把握开发重点🙄。
用户
五指棋小程序
小程序后台服务
ChatGPT服务
发送落子操作
同步用户的下棋位置
包装prompts语句,告诉GPT用户的下棋坐标
根据规则返回模型的下棋坐标
对返回的数据进行处理,封装小程序端的数据
对返回的数据进行处理,封装小程序端的数据
根据用户的下棋坐标以及前置的规则
直接返回模型的决策结果.
用户
五指棋小程序
小程序后台服务
ChatGPT服务
三、项目实现
3.1 ChatGPT API的接入
要接入ChatGPT API,需要按照以下步骤进行操作:
注册一个账号并登录到OpenAI的官网:
https://openai.com/
在Dashboard页面上,创建一个API密钥。在“API Keys”选项卡下,点击“Generate New Key”按钮。将生成的密钥保存好,以备后续使用。
选择所需的API服务,例如“Completion” API,以使用OpenAI的文本生成功能。
使用Python调用ChatGPT API实现代码如下:
方法一:使用request库
import requests
import json
# 构建API请求
url
= "https://api.openai.com/v1/engines/davinci-codex/completions"
headers
= {"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY"}
data
= {
"prompt": "Hello, my name is",
"max_tokens": 5
}
# 发送API请求
response
= requests
.post
(url
, headers
=headers
, data
=json
.dumps
(data
))
# 解析API响应
response_data
= json
.loads
(response
.text
)
generated_text
= response_data
["choices"][0]["text"]
print(generated_text
)
方式二:使用openAI库
from flask
import Flask
, request
import openai
app
= Flask
(__name__
)
openai
.api_key
= "YOUR_API_KEY_HERE"
@app.route("/")
def home():
return "Hello, World!"
@app.route("/chat", methods
=["POST"])
def chat():
data
= request
.json
response
= openai
.Completion
.create
(
engine
="davinci",
prompt
=data
["message"],
max_tokens
=60
)
return response
.choices
[0].text
if __name__
== "__main__":
app
.run
()
3.2 小程序端棋盘功能实现
主页
正在下棋用户卡片
等待下棋用户卡片
小程序界面实现代码如下:
<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]]) {