Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 聊天交互式游戏框架,井字棋游戏 #75

Merged
merged 4 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ data/
logs/

.sqllsrc.json
assets/games/tictactoe/gaming_board.png
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ python3 -m wechatter

- [x] **定时任务**:大部分命令均支持定时任务。需进行[配置](#%EF%B8%8F-task-cron-配置)。

## 支持的游戏

- [x] **井字棋**:双人游戏,图片游戏。
![tictactoe_show](docs/images/tictactoe_show.png)

> [!TIP]
> 游戏相关命令帮助请使用查阅[游戏基本命令](docs/command_show.md#游戏基本命令)。

## 支持的 Webhook

- [x] GitHub 仓库 Webhook,需在 GitHub 仓库 Settings 中添加 Webhook 并进行[配置](#%EF%B8%8F-github-webhook-配置)。
Expand Down
Binary file added assets/games/tictactoe/board.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/games/tictactoe/piece_o.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/games/tictactoe/piece_x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,5 @@ task_cron_list:
custom_command_key_dict:
gpt4: [ ">" ]
bili-hot: [ "bh" ]
play: [ "p" ]
weather: [ "w", "温度" ]
35 changes: 34 additions & 1 deletion docs/command_show.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
本文档展示了 WeChatter 支持的命令功能。

## 目录
- [游戏基本命令](#游戏基本命令)
- [创建游戏](#创建游戏)
- [加入游戏](#加入游戏)
- [开始游戏](#开始游戏)
- [进行游戏](#进行游戏)
- [结束游戏](#结束游戏)
- [查看游戏列表](#查看游戏列表)
- [天气预报](#天气预报)
- [待办清单](#待办清单)
- [添加待办](#添加待办)
Expand All @@ -28,6 +35,32 @@
- [冷知识](#冷知识)
- [历史上的今天](#历史上的今天)

## 游戏基本命令

### 创建游戏

![创建游戏](./images/cmd_game_create.png)

### 加入游戏

![加入游戏](./images/cmd_game_join.png)

### 开始游戏

![开始游戏](./images/cmd_game_start.png)

### 进行游戏

![进行游戏](./images/cmd_game_play.png)

### 结束游戏

![结束游戏](./images/cmd_game_over.png)

### 查看游戏列表

![查看游戏列表](./images/cmd_game_list.png)

## 天气预报

![获取天气预报](./images/cmd_weather.png)
Expand Down Expand Up @@ -124,4 +157,4 @@

## 历史上的今天

![历史上的今天](./images/cmd_today_in_history.png)
![历史上的今天](./images/cmd_today_in_history.png)
Binary file added docs/images/cmd_game_create.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/cmd_game_join.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/cmd_game_list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/cmd_game_over.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/cmd_game_play.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/cmd_game_start.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/tictactoe_show.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion wechatter/app/routers/wechat.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Person as DbPerson,
make_db_session,
)
from wechatter.games import games
from wechatter.message import MessageHandler
from wechatter.models.wechat import Message
from wechatter.models.wechat.group import Group
Expand Down Expand Up @@ -68,7 +69,9 @@ async def recv_wechat_msg(
print(str(message))

# 传入命令字典,构造消息处理器
message_handler = MessageHandler(commands=commands, quoted_handlers=quoted_handlers)
message_handler = MessageHandler(
commands=commands, quoted_handlers=quoted_handlers, games=games
)
# 用户发来的消息均送给消息解析器处理
message_handler.handle_message(message)

Expand Down
3 changes: 3 additions & 0 deletions wechatter/commands/_commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@
continue
# 动态导入模块
importlib.import_module("." + module_name, __package__)

# 释放变量
del command_files
4 changes: 1 addition & 3 deletions wechatter/commands/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ def __call__(self, func):
logger.error(error_message)
raise ValueError(error_message)
if len(params) == 3 and "message_obj" not in params:
error_message = (
f"参数名错误,命令处理函数的第3个参数必须为 message_obj{func.__name__}"
)
error_message = f"参数名错误,命令处理函数的第3个参数必须为 message_obj:{func.__name__}"
logger.error(error_message)
raise ValueError(error_message)
if len(params) > 3:
Expand Down
2 changes: 2 additions & 0 deletions wechatter/database/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .database import create_tables, make_db_session
from .tables import person_group_relation # noqa
from .tables.game_states import GameStates
from .tables.gpt_chat_info import GptChatInfo
from .tables.gpt_chat_message import GptChatMessage
from .tables.group import Group
Expand All @@ -16,4 +17,5 @@
"Group",
"Person",
"QuotedResponse",
"GameStates",
]
80 changes: 80 additions & 0 deletions wechatter/database/tables/game_states.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import json
from datetime import datetime
from typing import TYPE_CHECKING

from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship

from wechatter.database.tables import Base
from wechatter.models.game.game_states import GameStates as GameStatesModel
from wechatter.utils.unique_list import UniqueListDecoder, UniqueListEncoder

if TYPE_CHECKING:
from wechatter.database.tables.group import Group
from wechatter.database.tables.person import Person


class GameStates(Base):
"""
游戏状态表
"""

__tablename__ = "game_states"

id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
host_person_id: Mapped[str] = mapped_column(String, ForeignKey("person.id"))
host_group_id: Mapped[str] = mapped_column(
String, ForeignKey("group.id"), nullable=True
)
game_class_name: Mapped[str] = mapped_column(String)
states: Mapped[str] = mapped_column(String)
is_over: Mapped[bool] = mapped_column(Boolean, default=False)
create_time: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)

host_person: Mapped["Person"] = relationship(
"Person", back_populates="game_states_list"
)
host_group: Mapped["Group"] = relationship(
"Group", back_populates="game_states_list"
)

@classmethod
def from_model(cls, game_states_model: GameStatesModel):
group_id = None
if game_states_model.host_group:
group_id = game_states_model.host_group.id
return cls(
id=game_states_model.id,
host_person_id=game_states_model.host_person.id,
host_group_id=group_id,
game_class_name=game_states_model.game_class_name,
states=json.dumps(game_states_model.states, cls=UniqueListEncoder),
create_time=game_states_model.create_time,
is_over=game_states_model.is_over,
)

def to_model(self) -> GameStatesModel:
host_group = None
if self.host_group:
host_group = self.host_group.to_model()
return GameStatesModel(
id=self.id,
host_person=self.host_person.to_model(),
host_group=host_group,
game_class_name=self.game_class_name,
states=json.loads(self.states, cls=UniqueListDecoder),
create_time=self.create_time,
is_over=self.is_over,
)

def update(self, game_states_model: GameStatesModel):
group_id = None
if game_states_model.host_group:
group_id = game_states_model.host_group.id
self.host_person_id = game_states_model.host_person.id
self.host_group_id = group_id
self.game_class_name = game_states_model.game_class_name
self.states = json.dumps(game_states_model.states, cls=UniqueListEncoder)
self.create_time = game_states_model.create_time
self.is_over = game_states_model.is_over
return self
2 changes: 1 addition & 1 deletion wechatter/database/tables/gpt_chat_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@ def to_model(self) -> GptChatMessageModel:
id=self.id,
message=self.message.to_model(),
gpt_chat_info=self.gpt_chat_info.to_model(),
gp_response=self.gpt_response,
gpt_response=self.gpt_response,
)
8 changes: 8 additions & 0 deletions wechatter/database/tables/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from wechatter.models.wechat import Group as GroupModel

if TYPE_CHECKING:
from wechatter.database.tables.game_states import GameStates
from wechatter.database.tables.message import Message
from wechatter.database.tables.person import Person

Expand All @@ -21,19 +22,24 @@ class Group(Base):
id: Mapped[str] = mapped_column(String(100), primary_key=True)
name: Mapped[str]
alias: Mapped[Union[str, None]] = mapped_column(String, nullable=True)
is_gaming: Mapped[bool] = mapped_column(String, default=False)

members: Mapped[List["Person"]] = relationship(
"Person",
secondary="person_group_relation",
back_populates="groups",
)
messages: Mapped[List["Message"]] = relationship("Message", back_populates="group")
game_states_list: Mapped[List["GameStates"]] = relationship(
"GameStates", back_populates="host_group"
)

@classmethod
def from_model(cls, group_model: GroupModel):
return cls(
id=group_model.id,
name=group_model.name,
is_gaming=group_model.is_gaming,
)

def to_model(self) -> GroupModel:
Expand All @@ -44,6 +50,7 @@ def to_model(self) -> GroupModel:
id=self.id,
name=self.name,
member_list=member_list,
is_gaming=self.is_gaming,
)

def update(self, group_model: GroupModel):
Expand All @@ -52,3 +59,4 @@ def update(self, group_model: GroupModel):
for member in self.members:
member_list.append(Person.from_member_model(member))
self.members = member_list
self.is_gaming = group_model.is_gaming
8 changes: 8 additions & 0 deletions wechatter/database/tables/person.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from wechatter.models.wechat import Gender, GroupMember, Person as PersonModel

if TYPE_CHECKING:
from wechatter.database.tables.game_states import GameStates
from wechatter.database.tables.gpt_chat_info import GptChatInfo
from wechatter.database.tables.group import Group
from wechatter.database.tables.message import Message
Expand All @@ -29,6 +30,7 @@ class Person(Base):
is_star: Mapped[bool] = mapped_column(Boolean, default=False)
is_friend: Mapped[bool] = mapped_column(Boolean, default=False)
is_official_account: Mapped[bool] = mapped_column(Boolean, default=False)
is_gaming: Mapped[bool] = mapped_column(Boolean, default=False)

groups: Mapped[List["Group"]] = relationship(
"Group",
Expand All @@ -39,6 +41,9 @@ class Person(Base):
gpt_chat_infos: Mapped[List["GptChatInfo"]] = relationship(
"GptChatInfo", back_populates="person"
)
game_states_list: Mapped[List["GameStates"]] = relationship(
"GameStates", back_populates="host_person"
)

@classmethod
def from_model(cls, person_model: PersonModel):
Expand All @@ -52,6 +57,7 @@ def from_model(cls, person_model: PersonModel):
is_star=person_model.is_star,
is_friend=person_model.is_friend,
is_official_account=person_model.is_official_account,
is_gaming=person_model.is_gaming,
)

@classmethod
Expand All @@ -74,6 +80,7 @@ def to_model(self) -> PersonModel:
is_star=self.is_star,
is_friend=self.is_friend,
is_official_account=self.is_official_account,
is_gaming=self.is_gaming,
)

def update(self, person_model: PersonModel):
Expand All @@ -85,3 +92,4 @@ def update(self, person_model: PersonModel):
self.is_star = person_model.is_star
self.is_friend = person_model.is_friend
self.is_official_account = person_model.is_official_account
self.is_gaming = person_model.is_gaming
Loading
Loading