Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Y
yugioh-ccb
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
yugioh-ccb
Commits
c8746b10
Commit
c8746b10
authored
May 02, 2025
by
EN1AK
Committed by
GitHub
May 02, 2025
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add files via upload
parent
8fcf3768
Changes
3
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
752 additions
and
41 deletions
+752
-41
guess_card_game.py
guess_card_game.py
+85
-36
map.py
map.py
+590
-0
templates/index.html
templates/index.html
+77
-5
No files found.
guess_card_game.py
View file @
c8746b10
...
@@ -9,33 +9,7 @@ db = None
...
@@ -9,33 +9,7 @@ db = None
target_row
=
None
target_row
=
None
app
.
secret_key
=
"你自己的随机 Secret Key"
app
.
secret_key
=
"你自己的随机 Secret Key"
# --- 与原 CLI 版本相同的配置 ---
from
map
import
SETNAME_MAP
,
RACE_MAP
,
TYPE_MAP
,
CATEGORY_TAGS
,
TYPE_LINK
,
LINK_MARKERS
,
SETNAME_MAP
,
ATTR_MAP
ATTR_MAP
=
{
0x01
:
"地"
,
0x02
:
"水"
,
0x04
:
"炎"
,
0x08
:
"风"
,
0x10
:
"光"
,
0x20
:
"暗"
,
0x40
:
"神"
}
RACE_MAP
=
{
0x1
:
"战士"
,
0x2
:
"魔法师"
,
0x4
:
"天使"
,
0x8
:
"恶魔"
,
0x10
:
"不死"
,
0x20
:
"机械"
,
0x40
:
"水"
,
0x80
:
"炎"
,
0x100
:
"岩石"
,
0x200
:
"鸟兽"
,
0x400
:
"植物"
,
0x800
:
"昆虫"
,
0x1000
:
"雷"
,
0x2000
:
"龙"
,
0x4000
:
"兽"
,
0x8000
:
"兽战士"
,
0x10000
:
"恐龙"
,
0x20000
:
"鱼"
,
0x40000
:
"海龙"
,
0x80000
:
"爬虫"
,
0x100000
:
"念动力"
,
0x200000
:
"幻神兽"
,
0x400000
:
"创造神"
,
0x800000
:
"幻龙"
,
0x1000000
:
"电子界"
,
0x2000000
:
"幻想魔"
,
}
TYPE_MAP
=
{
0x1
:
"怪兽"
,
0x2
:
"魔法"
,
0x4
:
"陷阱"
,
0x10
:
"通常"
,
0x20
:
"效果"
,
0x40
:
"融合"
,
0x80
:
"仪式"
,
0x100
:
"陷阱怪兽"
,
0x200
:
"灵魂"
,
0x400
:
"同盟"
,
0x800
:
"二重"
,
0x1000
:
"调整"
,
0x2000
:
"同调"
,
0x4000
:
"衍生物"
,
0x10000
:
"速攻"
,
0x20000
:
"永续"
,
0x40000
:
"装备"
,
0x80000
:
"场地"
,
0x100000
:
"反击"
,
0x200000
:
"翻转"
,
0x400000
:
"卡通"
,
0x800000
:
"超量"
,
0x1000000
:
"灵摆"
,
0x2000000
:
"特殊召唤"
,
0x4000000
:
"连接"
}
CATEGORY_TAGS
=
{
1100
:
'魔陷破坏'
,
1101
:
'怪兽破坏'
,
1102
:
'卡片除外'
,
1103
:
'送去墓地'
,
1104
:
'返回手卡'
,
1105
:
'返回卡组'
,
1106
:
'手卡破坏'
,
1107
:
'卡组破坏'
,
1108
:
'抽卡辅助'
,
1109
:
'卡组检索'
,
1110
:
'卡片回收'
,
1111
:
'表示形式'
,
1112
:
'控制权'
,
1113
:
'攻守变化'
,
1114
:
'穿刺伤害'
,
1115
:
'多次攻击'
,
1116
:
'攻击限制'
,
1117
:
'直接攻击'
,
1118
:
'特殊召唤'
,
1119
:
'衍生物'
,
1120
:
'种族相关'
,
1121
:
'属性相关'
,
1122
:
'LP伤害'
,
1123
:
'LP回复'
,
1124
:
'破坏耐性'
,
1125
:
'效果耐性'
,
1126
:
'指示物'
,
1127
:
'幸运'
,
1128
:
'融合相关'
,
1129
:
'同调相关'
,
1130
:
'超量相关'
,
1131
:
'效果无效'
}
def
parse_flags
(
value
,
mapping
):
def
parse_flags
(
value
,
mapping
):
return
[
name
for
bit
,
name
in
mapping
.
items
()
if
value
&
bit
]
return
[
name
for
bit
,
name
in
mapping
.
items
()
if
value
&
bit
]
...
@@ -43,11 +17,38 @@ def parse_flags(value, mapping):
...
@@ -43,11 +17,38 @@ def parse_flags(value, mapping):
def
parse_category
(
cat
):
def
parse_category
(
cat
):
return
[
CATEGORY_TAGS
[
1100
+
i
]
for
i
in
range
(
64
)
if
(
cat
>>
i
)
&
1
and
(
1100
+
i
)
in
CATEGORY_TAGS
]
return
[
CATEGORY_TAGS
[
1100
+
i
]
for
i
in
range
(
64
)
if
(
cat
>>
i
)
&
1
and
(
1100
+
i
)
in
CATEGORY_TAGS
]
def
parse_setcode
(
setcode
,
name_map
):
# 1. 转成大写十六进制字符串
hex_str
=
f
"{setcode:X}"
# 2. 左侧补零,使长度成为 4 的倍数
pad_len
=
(
-
len
(
hex_str
))
%
4
if
pad_len
:
hex_str
=
hex_str
.
zfill
(
len
(
hex_str
)
+
pad_len
)
# 3. 每 4 位一组
names
=
[]
for
i
in
range
(
0
,
len
(
hex_str
),
4
):
segment
=
hex_str
[
i
:
i
+
4
]
# 全 0 的段跳过
if
segment
==
"0000"
:
continue
code
=
int
(
segment
,
16
)
if
code
in
name_map
:
names
.
append
(
name_map
[
code
])
return
names
def
extract_arrows
(
def_value
):
"""
从 link_marker 的整数值中提取出 所有 生效的箭头符号,返回一个列表。
"""
return
[
sym
for
bit
,
sym
in
LINK_MARKERS
.
items
()
if
def_value
&
bit
]
def
load_card_database
(
path
):
def
load_card_database
(
path
):
conn
=
sqlite3
.
connect
(
path
)
conn
=
sqlite3
.
connect
(
path
)
# 先把两个表读进 DataFrame
# 先把两个表读进 DataFrame
datas
=
pd
.
read_sql_query
(
datas
=
pd
.
read_sql_query
(
"SELECT id, type, atk, def, level, race, attribute, category ,hot FROM datas"
,
"SELECT id, type, atk, def, level, race, attribute, category ,hot
,setcode
FROM datas"
,
conn
,
index_col
=
"id"
conn
,
index_col
=
"id"
)
)
texts
=
pd
.
read_sql_query
(
texts
=
pd
.
read_sql_query
(
...
@@ -64,20 +65,44 @@ def load_card_database(path):
...
@@ -64,20 +65,44 @@ def load_card_database(path):
return
df
return
df
def
card_to_tags
(
row
):
def
card_to_tags
(
row
):
is_link
=
bool
(
row
[
"type"
]
&
TYPE_LINK
)
# 链接怪兽的“守备”清空
defense
=
""
if
is_link
else
row
[
"def"
]
# 如果是链接怪兽,从 link_marker 提取箭头
arrows
=
extract_arrows
(
row
[
"def"
])
if
is_link
else
[]
return
{
return
{
"卡名"
:
row
[
"name"
],
"卡名"
:
row
[
"name"
],
"攻击"
:
row
[
"atk"
],
"攻击"
:
row
[
"atk"
],
"守备"
:
row
[
"def"
]
,
"守备"
:
defense
,
"等级"
:
row
[
"level"
]
&
0xFF
,
"等级"
:
row
[
"level"
]
&
0xFF
,
"箭头"
:
arrows
,
"刻度"
:
(
row
[
"level"
]
>>
24
)
&
0xFF
,
"刻度"
:
(
row
[
"level"
]
>>
24
)
&
0xFF
,
"类型"
:
parse_flags
(
row
[
"type"
],
TYPE_MAP
),
"类型"
:
parse_flags
(
row
[
"type"
],
TYPE_MAP
),
"属性"
:
ATTR_MAP
.
get
(
row
[
"attribute"
],
f
"0x{row['attribute']:X}"
),
"属性"
:
ATTR_MAP
.
get
(
row
[
"attribute"
],
f
"0x{row['attribute']:X}"
),
"种族"
:
RACE_MAP
.
get
(
row
[
"race"
],
f
"0x{row['race']:X}"
),
"种族"
:
RACE_MAP
.
get
(
row
[
"race"
],
f
"0x{row['race']:X}"
),
"效果标签"
:
parse_category
(
row
[
"category"
])
"效果标签"
:
parse_category
(
row
[
"category"
]),
"系列"
:
parse_setcode
(
row
[
"setcode"
],
SETNAME_MAP
),
}
}
def
compare_tags
(
guess_tags
,
answer_tags
):
def
compare_tags
(
guess_tags
,
answer_tags
):
def
cmp
(
key
,
val1
,
val2
):
def
cmp
(
key
,
val1
,
val2
):
if
val1
is
None
or
val1
==
""
or
val2
is
None
or
val2
==
""
:
# 要么是用户没猜,要么目标也无该字段,都算“未猜”
return
'<span class="partial">—</span>'
if
key
==
"箭头"
:
pills
=
[]
# 对八个方向都展示一个小标签
for
bit
,
sym
in
LINK_MARKERS
.
items
():
if
sym
in
val1
:
# 猜的里有
cls
=
"tag-green"
if
sym
in
val2
else
"tag-red"
else
:
# 猜的里没有
cls
=
"tag-gray"
pills
.
append
(
f
'<span class="tag {cls}">{sym}</span>'
)
return
" "
.
join
(
pills
)
# 数值型字段:攻击、守备、等级、刻度
# 数值型字段:攻击、守备、等级、刻度
if
isinstance
(
val1
,
numbers
.
Number
):
if
isinstance
(
val1
,
numbers
.
Number
):
diff
=
abs
(
val1
-
val2
)
diff
=
abs
(
val1
-
val2
)
...
@@ -106,13 +131,13 @@ def compare_tags(guess_tags, answer_tags):
...
@@ -106,13 +131,13 @@ def compare_tags(guess_tags, answer_tags):
pills
=
[]
pills
=
[]
for
t
in
val1
:
for
t
in
val1
:
# 猜的 tag 在目标里才 green,否则 red
# 猜的 tag 在目标里才 green,否则 red
cls
=
"tag-green"
if
t
in
val2
else
"tag-
red
"
cls
=
"tag-green"
if
t
in
val2
else
"tag-
gray
"
pills
.
append
(
f
'<span class="tag {cls}">{t}</span>'
)
pills
.
append
(
f
'<span class="tag {cls}">{t}</span>'
)
return
" "
.
join
(
pills
)
or
'<span class="tag tag-gray">—</span>'
return
" "
.
join
(
pills
)
or
'<span class="tag tag-gray">—</span>'
# 其它(字符串等)完全匹配才 green,否则 gray
# 其它(字符串等)完全匹配才 green,否则 gray
else
:
else
:
cls
=
"tag-green"
if
val1
==
val2
else
"tag-gray"
cls
=
"tag-green"
if
val1
==
val2
else
"tag-gray
"
return
f
'<span class="tag {cls}">{val1}</span>'
return
f
'<span class="tag {cls}">{val1}</span>'
return
{
return
{
...
@@ -150,6 +175,7 @@ def start():
...
@@ -150,6 +175,7 @@ def start():
# 随机选一个 target_id
# 随机选一个 target_id
pool
=
filter_db
(
mode
)
pool
=
filter_db
(
mode
)
session
[
'target_id'
]
=
int
(
pool
.
sample
(
1
)
.
index
[
0
])
session
[
'target_id'
]
=
int
(
pool
.
sample
(
1
)
.
index
[
0
])
#session['target_id'] = 71818935
# 重置本局提示相关状态
# 重置本局提示相关状态
session
[
'guess_count'
]
=
0
session
[
'guess_count'
]
=
0
session
[
'hints_shown'
]
=
[]
session
[
'hints_shown'
]
=
[]
...
@@ -160,9 +186,16 @@ def start():
...
@@ -160,9 +186,16 @@ def start():
def
game
():
def
game
():
feedback
=
None
feedback
=
None
mode
=
session
.
get
(
'mode'
)
mode
=
session
.
get
(
'mode'
)
if
not
mode
or
'target_id'
not
in
session
:
if
not
mode
:
return
redirect
(
url_for
(
"start"
))
return
redirect
(
url_for
(
"start"
))
if
'target_id'
not
in
session
:
pool
=
filter_db
(
mode
)
session
[
'target_id'
]
=
int
(
pool
.
sample
(
1
)
.
index
[
0
])
session
[
'history'
]
=
[]
session
[
'hints'
]
=
[]
session
[
'hinted_chars'
]
=
[]
filtered
=
filter_db
(
mode
)
filtered
=
filter_db
(
mode
)
target
=
db
.
loc
[
session
[
'target_id'
]]
target
=
db
.
loc
[
session
[
'target_id'
]]
...
@@ -174,6 +207,13 @@ def game():
...
@@ -174,6 +207,13 @@ def game():
if
request
.
method
==
"POST"
:
if
request
.
method
==
"POST"
:
action
=
request
.
form
.
get
(
"action"
,
"guess"
)
action
=
request
.
form
.
get
(
"action"
,
"guess"
)
if
action
==
"change_mode"
:
new_mode
=
request
.
form
.
get
(
"mode"
)
session
[
'mode'
]
=
new_mode
# 直接把上一行 target_id 删掉,触发上面自动重置
session
.
pop
(
'target_id'
,
None
)
return
redirect
(
url_for
(
"game"
))
if
action
==
"surrender"
:
if
action
==
"surrender"
:
# 认输
# 认输
feedback
=
{
"giveup"
:
True
,
"answer"
:
target
[
"name"
],
"hints"
:
hints
}
feedback
=
{
"giveup"
:
True
,
"answer"
:
target
[
"name"
],
"hints"
:
hints
}
...
@@ -189,7 +229,7 @@ def game():
...
@@ -189,7 +229,7 @@ def game():
session
.
pop
(
'history'
,
None
)
session
.
pop
(
'history'
,
None
)
session
.
pop
(
'hints'
,
None
)
session
.
pop
(
'hints'
,
None
)
session
.
pop
(
'hinted_chars'
,
None
)
session
.
pop
(
'hinted_chars'
,
None
)
return
redirect
(
url_for
(
"
start
"
))
return
redirect
(
url_for
(
"
game
"
))
else
:
else
:
# 普通猜测
# 普通猜测
...
@@ -202,9 +242,17 @@ def game():
...
@@ -202,9 +242,17 @@ def game():
else
:
else
:
guess
=
match
.
iloc
[
0
]
guess
=
match
.
iloc
[
0
]
if
guess
.
name
==
target
.
name
:
if
guess
.
name
==
target
.
name
:
# 猜中
# 1. 先做一次对比
compare
=
compare_tags
(
card_to_tags
(
guess
),
card_to_tags
(
target
))
# 2. 把这条全绿记录追加到本局历史
history
.
append
({
"guess_name"
:
guess
[
'name'
],
"compare"
:
compare
})
# 3. 带上 compare 和 hints 给模板渲染
feedback
=
{
feedback
=
{
"success"
:
f
"🎉 恭喜你猜中了!答案就是【{guess['name']}】"
,
"success"
:
f
"🎉 恭喜你猜中了!答案就是【{guess['name']}】"
,
"compare"
:
compare
,
"hints"
:
hints
"hints"
:
hints
}
}
# 清理本局 session
# 清理本局 session
...
@@ -258,7 +306,8 @@ def game():
...
@@ -258,7 +306,8 @@ def game():
return
render_template
(
"index.html"
,
return
render_template
(
"index.html"
,
feedback
=
feedback
,
feedback
=
feedback
,
history
=
history
,
history
=
history
,
hints
=
hints
)
hints
=
hints
,
mode
=
mode
)
@
app
.
route
(
"/suggest"
)
@
app
.
route
(
"/suggest"
)
def
suggest
():
def
suggest
():
...
...
map.py
0 → 100644
View file @
c8746b10
This diff is collapsed.
Click to expand it.
templates/index.html
View file @
c8746b10
...
@@ -34,20 +34,85 @@
...
@@ -34,20 +34,85 @@
.partial
{
background-color
:
#fff9c4
;
}
.partial
{
background-color
:
#fff9c4
;
}
.mismatch
{
background-color
:
#ffcdd2
;
}
.mismatch
{
background-color
:
#ffcdd2
;
}
button
{
margin-left
:
8px
;
}
button
{
margin-left
:
8px
;
}
.hint-box
{
margin
:
1em
0
;
padding
:
0.5em
;
background
:
#fff3cd
;
border
:
1px
solid
#ffeeba
;
border-radius
:
4px
;
color
:
#856404
;
}
.arrow-grid
{
display
:
grid
;
grid-template-rows
:
repeat
(
3
,
1.5em
);
grid-template-columns
:
repeat
(
3
,
1.5em
);
gap
:
2px
;
}
.arrow-cell
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
1.2em
;
border
:
1px
solid
#ccc
;
border-radius
:
4px
;
}
.arrow-cell.empty
{
border
:
none
;
background
:
transparent
;
}
.arrow-cell.cell-green
{
background
:
#c8e6c9
;
}
.arrow-cell.cell-red
{
background
:
#ffcdd2
;
}
.arrow-cell.cell-black
{
background
:
#eeeeee
;
}
.tag
{
display
:
inline-block
;
padding
:
2px
6px
;
margin
:
1px
;
border-radius
:
4px
;
font-size
:
0.9em
;
}
.tag-green
{
background
:
#c8e6c9
;
}
.tag-yellow
{
background
:
#fff9c4
;
}
.tag-gray
{
background
:
#eee
;
}
.tag-red
{
background
:
#ffcdd2
;
}
</style>
</style>
</head>
</head>
<body>
<body>
<h1>
🎴 游戏王CCB
</h1>
<h1>
🎴 游戏王CCB
</h1>
<form
method=
"POST"
style=
"margin-bottom:1em;"
>
<label>
题库:
<select
name=
"mode"
>
<option
value=
"monster"
{%
if
mode =
='monster'
%}
selected
{%
endif
%}
>
怪兽卡(无通常)
</option>
<option
value=
"spell"
{%
if
mode =
='spell'
%}
selected
{%
endif
%}
>
魔法卡
</option>
<option
value=
"trap"
{%
if
mode =
='trap'
%}
selected
{%
endif
%}
>
陷阱卡
</option>
<option
value=
"hot"
{%
if
mode =
='hot'
%}
selected
{%
endif
%}
>
热门卡
</option>
<option
value=
"all"
{%
if
mode =
='all'
%}
selected
{%
endif
%}
>
所有卡
</option>
</select>
</label>
<button
type=
"submit"
name=
"action"
value=
"change_mode"
>
切换题库
</button>
</form>
<!-- 历史猜测 -->
<!-- 历史猜测 -->
{% if history %}
{% if history %}
<h2>
历史猜测
</h2>
<h2>
历史猜测
</h2>
<table>
<table>
<thead>
<thead>
<tr>
<tr>
<th>
卡名
</th><th>
攻击
</th><th>
守备
</th><th>
等级
</th><th>
刻度
</th>
<th>
卡名
</th><th>
攻击
</th><th>
守备
</th><th>
等级
</th><th>
刻度
</th>
<th>
箭头
</th>
<th>
类型
</th><th>
属性
</th><th>
种族
</th><th>
效果标签
</th>
<th>
类型
</th><th>
属性
</th><th>
种族
</th><th>
效果标签
</th>
<th>
系列
</th>
</tr>
</tr>
</thead>
</thead>
<tbody>
<tbody>
...
@@ -58,10 +123,12 @@
...
@@ -58,10 +123,12 @@
<td>
{{ entry.compare['守备']|safe }}
</td>
<td>
{{ entry.compare['守备']|safe }}
</td>
<td>
{{ entry.compare['等级']|safe }}
</td>
<td>
{{ entry.compare['等级']|safe }}
</td>
<td>
{{ entry.compare['刻度']|safe }}
</td>
<td>
{{ entry.compare['刻度']|safe }}
</td>
<td>
{{ entry.compare['箭头']|safe }}
</td>
<td
style=
"white-space: nowrap;"
>
{{ entry.compare['类型']|safe }}
</td>
<td
style=
"white-space: nowrap;"
>
{{ entry.compare['类型']|safe }}
</td>
<td
style=
"white-space: nowrap;"
>
{{ entry.compare['属性']|safe }}
</td>
<td
style=
"white-space: nowrap;"
>
{{ entry.compare['属性']|safe }}
</td>
<td
style=
"white-space: nowrap;"
>
{{ entry.compare['种族']|safe }}
</td>
<td
style=
"white-space: nowrap;"
>
{{ entry.compare['种族']|safe }}
</td>
<td
style=
"white-space: nowrap;"
>
{{ entry.compare['效果标签']|safe }}
</td>
<td
style=
"white-space: nowrap;"
>
{{ entry.compare['效果标签']|safe }}
</td>
<td
style=
"white-space: nowrap;"
>
{{ entry.compare['系列']|safe }}
</td>
</tr>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</tbody>
...
@@ -78,6 +145,14 @@
...
@@ -78,6 +145,14 @@
<ul
id=
"suggestions"
hidden
></ul>
<ul
id=
"suggestions"
hidden
></ul>
</form>
</form>
{% if hints %}
<div
class=
"hint-box"
>
{% for h in hints %}
<p>
{{ h }}
</p>
{% endfor %}
</div>
{% endif %}
<!-- 本次反馈 -->
<!-- 本次反馈 -->
{% if feedback %}
{% if feedback %}
{% if feedback.error %}
{% if feedback.error %}
...
@@ -87,9 +162,6 @@
...
@@ -87,9 +162,6 @@
{% elif feedback.giveup %}
{% elif feedback.giveup %}
<p
style=
"color:blue; font-weight:bold;"
>
💡 放弃了!正确答案是:{{ feedback.answer }}
</p>
<p
style=
"color:blue; font-weight:bold;"
>
💡 放弃了!正确答案是:{{ feedback.answer }}
</p>
{% elif feedback.compare %}
{% elif feedback.compare %}
{% if feedback.hint %}
<p
style=
"color:blue;"
>
💡 提示:卡名中包含 “{{ feedback.hint }}” 这个字
</p>
{% endif %}
<h3>
你猜的是:
<strong>
{{ feedback.guess_name }}
</strong></h3>
<h3>
你猜的是:
<strong>
{{ feedback.guess_name }}
</strong></h3>
<table>
<table>
<thead><tr><th>
属性
</th><th>
结果
</th></tr></thead>
<thead><tr><th>
属性
</th><th>
结果
</th></tr></thead>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment