Skip to content

Commit

Permalink
Merge pull request #75 from Cassius0924/feat-game
Browse files Browse the repository at this point in the history
feat: 聊天交互式游戏框架,井字棋游戏
  • Loading branch information
Cassius0924 authored Feb 27, 2024
2 parents c4d7022 + e42ae33 commit ff35fdc
Show file tree
Hide file tree
Showing 33 changed files with 1,054 additions and 89 deletions.
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

0 comments on commit ff35fdc

Please sign in to comment.