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
3aaf959b
Commit
3aaf959b
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
e05ada0d
Changes
3
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
275 additions
and
195 deletions
+275
-195
data_utils.py
data_utils.py
+172
-0
guess_card_game.py
guess_card_game.py
+69
-188
templates/start.html
templates/start.html
+34
-7
No files found.
data_utils.py
0 → 100644
View file @
3aaf959b
import
sqlite3
import
pandas
as
pd
import
numbers
from
pathlib
import
Path
import
sys
from
map
import
RACE_MAP
,
TYPE_MAP
,
CATEGORY_TAGS
,
TYPE_LINK
,
LINK_MARKERS
,
SETNAME_MAP
,
ATTR_MAP
def
parse_flags
(
value
,
mapping
):
return
[
name
for
bit
,
name
in
mapping
.
items
()
if
value
&
bit
]
def
parse_category
(
cat
):
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
:
str
=
None
)
->
pd
.
DataFrame
:
"""
加载 cards.cdb 里的 datas 和 texts 两张表,
合并、去重、按 id 排序后返回一个 DataFrame。
如果不传入 path,则自动:
· 在 PyInstaller 打包后的环境中,从 sys._MEIPASS 找到临时目录里的 cards.cdb
· 否则从当前脚本同级目录下加载 cards.cdb
"""
# 1. 自动定位数据库文件
if
path
is
None
:
# PyInstaller 打包后会把数据放到 _MEIPASS 里
base
=
getattr
(
sys
,
"_MEIPASS"
,
None
)
if
base
is
None
:
# 普通脚本运行,数据库和脚本在同一个目录
base
=
Path
(
__file__
)
.
parent
else
:
# 打包执行时,_MEIPASS 已经是一个 str 临时目录
base
=
Path
(
base
)
db_file
=
base
/
"cards.cdb"
else
:
db_file
=
Path
(
path
)
if
not
db_file
.
exists
():
raise
FileNotFoundError
(
f
"找不到数据库文件:{db_file}"
)
# 2. 连接并读取表
conn
=
sqlite3
.
connect
(
str
(
db_file
))
datas
=
pd
.
read_sql_query
(
"SELECT id, type, atk, def, level, race, attribute, category, hot, setcode FROM datas"
,
conn
,
index_col
=
"id"
)
texts
=
pd
.
read_sql_query
(
"SELECT id, name FROM texts"
,
conn
,
index_col
=
"id"
)
conn
.
close
()
# 3. 合并去重并返回
df
=
datas
.
join
(
texts
,
how
=
"inner"
)
.
reset_index
()
df
=
(
df
.
sort_values
(
"id"
)
.
drop_duplicates
(
subset
=
"name"
,
keep
=
"first"
)
.
set_index
(
"id"
)
)
return
df
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
{
"卡名"
:
row
[
"name"
],
"攻击"
:
row
[
"atk"
],
"守备"
:
defense
,
"等级"
:
row
[
"level"
]
&
0xFF
,
"箭头"
:
arrows
,
"刻度"
:
(
row
[
"level"
]
>>
24
)
&
0xFF
,
"类型"
:
parse_flags
(
row
[
"type"
],
TYPE_MAP
),
"属性"
:
ATTR_MAP
.
get
(
row
[
"attribute"
],
f
"0x{row['attribute']:X}"
),
"种族"
:
RACE_MAP
.
get
(
row
[
"race"
],
f
"0x{row['race']:X}"
),
"效果标签"
:
parse_category
(
row
[
"category"
]),
"系列"
:
parse_setcode
(
row
[
"setcode"
],
SETNAME_MAP
),
}
def
compare_tags
(
guess_tags
,
answer_tags
):
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
):
diff
=
abs
(
val1
-
val2
)
# 先判断完全相等
if
diff
==
0
:
cls
=
"tag-green"
else
:
if
key
in
(
"攻击"
,
"守备"
):
if
diff
<=
500
:
cls
=
"tag-yellow"
else
:
cls
=
"tag-gray"
elif
key
in
(
"等级"
,
"刻度"
):
if
diff
<=
2
:
cls
=
"tag-yellow"
else
:
cls
=
"tag-gray"
else
:
cls
=
"tag-gray"
# 箭头
arrow
=
""
if
diff
==
0
else
(
"↑"
if
val1
<
val2
else
"↓"
)
return
f
'<span class="tag {cls}">{val1}{arrow}</span>'
# 列表型字段:如 类型、效果标签……
elif
isinstance
(
val1
,
list
):
pills
=
[]
for
t
in
val1
:
# 猜的 tag 在目标里才 green,否则 red
cls
=
"tag-green"
if
t
in
val2
else
"tag-gray"
pills
.
append
(
f
'<span class="tag {cls}">{t}</span>'
)
return
" "
.
join
(
pills
)
or
'<span class="tag tag-gray">—</span>'
# 其它(字符串等)完全匹配才 green,否则 gray
else
:
cls
=
"tag-green"
if
val1
==
val2
else
"tag-gray "
return
f
'<span class="tag {cls}">{val1}</span>'
return
{
key
:
cmp
(
key
,
guess_tags
[
key
],
answer_tags
[
key
])
for
key
in
guess_tags
}
guess_card_game.py
View file @
3aaf959b
This diff is collapsed.
Click to expand it.
templates/start.html
View file @
3aaf959b
...
...
@@ -3,16 +3,43 @@
<head>
<meta
charset=
"UTF-8"
>
<title>
开始新游戏
</title>
<style>
body
{
font-family
:
sans-serif
;
padding
:
20px
;
}
fieldset
{
margin-bottom
:
1em
;
}
.slider-label
{
display
:
flex
;
align-items
:
center
;
}
.slider-label
span
{
margin-left
:
0.5em
;
font-weight
:
bold
;
}
</style>
</head>
<body>
<h1>
选择本次游戏的卡片范围
</h1>
<form
method=
"post"
>
<label><input
type=
"radio"
name=
"mode"
value=
"monster"
required
>
怪兽卡(排除通常怪兽)
</label><br>
<label><input
type=
"radio"
name=
"mode"
value=
"spell"
>
魔法卡
</label><br>
<label><input
type=
"radio"
name=
"mode"
value=
"trap"
>
陷阱卡
</label><br>
<label><input
type=
"radio"
name=
"mode"
value=
"all"
>
全部卡片
</label><br>
<label><input
type=
"radio"
name=
"mode"
value=
"hot"
>
热门怪兽卡
</label><br>
<br>
<form
method=
"post"
action=
"/"
>
<fieldset>
<legend>
卡片类型
</legend>
<label><input
type=
"radio"
name=
"mode"
value=
"monster"
required
>
怪兽卡(排除通常怪兽)
</label><br>
<label><input
type=
"radio"
name=
"mode"
value=
"spell"
>
魔法卡
</label><br>
<label><input
type=
"radio"
name=
"mode"
value=
"trap"
>
陷阱卡
</label><br>
<label><input
type=
"radio"
name=
"mode"
value=
"all"
>
全部卡片
</label><br>
<label><input
type=
"radio"
name=
"mode"
value=
"hot"
>
热门怪兽卡
</label><br>
</fieldset>
<fieldset>
<legend>
猜测次数
</legend>
<div
class=
"slider-label"
>
<label
for=
"attempts"
>
次数:
</label>
<input
type=
"range"
id=
"attempts"
name=
"attempts"
min=
"5"
max=
"10"
value=
"5"
oninput=
"document.getElementById('attempts-value').innerText = this.value"
>
<span
id=
"attempts-value"
>
5
</span>
次
</div>
<small>
拖动滑块选择本次可以猜测的最大次数(5–10 次)。
</small>
</fieldset>
<button
type=
"submit"
>
开始游戏
</button>
</form>
</body>
...
...
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