Skip to content

Commit 36400cd

Browse files
committed
update ai dialogue
1 parent 7f3fc29 commit 36400cd

7 files changed

Lines changed: 773 additions & 60 deletions

File tree

src/App.vue

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
:is-game-over="isGameOver" :is-flipped="isFlipped" :board="board" :player-color="playerColor"
3434
:white-time-seconds="whiteTimeSeconds"
3535
:black-time-seconds="blackTimeSeconds" :active-color="currentTurn" :clock-test-id="'sidebar-chess-clock'"
36-
:game-mode="gameMode"
36+
:game-mode="gameMode" :dialogue-text="dialogueText" :dialogue-key="dialogueKey"
3737
v-model:is-sound-enabled="isSoundEnabled" v-model:coordinate-label-mode="coordinateLabelMode"
3838
@toggle-flip="isFlipped = !isFlipped" :has-game-started="hasGameStarted" @undo="handleUndo"
3939
@draw="handleDrawOffer" @resign="handleResign" @restart="handleRestart" @back-to-home="handleBackToHome" />
@@ -65,6 +65,7 @@ import BoardPanel from './components/BoardPanel.vue'
6565
import { useSettings } from './composables/useSettings'
6666
import { useBoardDisplay } from './composables/useBoardDisplay'
6767
import { useGameState } from './composables/useGameState'
68+
import { useDragonDialogue } from './composables/useDragonDialogue'
6869
import settingSvg from './assets/icon/setting.svg?raw'
6970
import githubSvg from './assets/icon/github.svg?raw'
7071
@@ -119,6 +120,8 @@ const {
119120
mousePos,
120121
premove,
121122
canPremove,
123+
isDrawByStalemate,
124+
isDrawByInsufficientMaterial,
122125
handleMouseDown,
123126
handleTouchStart,
124127
handleTouchMove,
@@ -135,6 +138,23 @@ const {
135138
stopClock,
136139
} = game
137140
141+
// ---- 龙语对话 ----
142+
const { currentDialogue: dialogueText, dialogueKey } = useDragonDialogue(
143+
board,
144+
currentTurn,
145+
moveHistory,
146+
gameMode,
147+
playerColor,
148+
isGameOver,
149+
gameStatusMessage,
150+
halfmoveClock,
151+
getPositionCount,
152+
isDrawByStalemate,
153+
isDrawByInsufficientMaterial,
154+
hasResigned,
155+
timeoutWinner,
156+
)
157+
138158
// ---- 远程对局 ----
139159
const handleRemoteGame = () => {
140160
// TODO: 后续实现远程对局功能
1.1 KB
Loading

src/components/DragonDialogue.vue

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<template>
2+
<div v-if="isAiEnabled" class="card dialogue-card" :key="dialogueKey">
3+
<div class="ai-avatar">
4+
<img :src="avatarSrc" alt="Aussir" class="avatar-img" />
5+
</div>
6+
<div class="dialogue-bubble">
7+
<span class="dialogue-text">{{ displayText }}</span>
8+
</div>
9+
</div>
10+
</template>
11+
12+
<script setup lang="ts">
13+
import { ref, watch, onUnmounted } from 'vue'
14+
import avatarSrc from '../assets/texture/avatar/aussir.png'
15+
16+
interface Props {
17+
text: string
18+
dialogueKey: number
19+
isAiEnabled: boolean
20+
}
21+
22+
const props = defineProps<Props>()
23+
24+
const displayText = ref('')
25+
const fullText = ref('')
26+
let typeTimer: ReturnType<typeof setInterval> | null = null
27+
let charIndex = 0
28+
29+
function startTypewriter() {
30+
stopTypewriter()
31+
fullText.value = props.text
32+
displayText.value = ''
33+
charIndex = 0
34+
35+
if (!props.text) return
36+
37+
// 打字速度:每字约 40ms
38+
typeTimer = setInterval(() => {
39+
charIndex++
40+
displayText.value = fullText.value.slice(0, charIndex)
41+
if (charIndex >= fullText.value.length) {
42+
stopTypewriter()
43+
}
44+
}, 40)
45+
}
46+
47+
function stopTypewriter() {
48+
if (typeTimer !== null) {
49+
clearInterval(typeTimer)
50+
typeTimer = null
51+
}
52+
}
53+
54+
// 当台词内容或 key 变化时,重新开始打字
55+
watch(
56+
() => [props.text, props.dialogueKey] as const,
57+
() => {
58+
startTypewriter()
59+
},
60+
{ immediate: true },
61+
)
62+
63+
onUnmounted(() => {
64+
stopTypewriter()
65+
})
66+
</script>
67+
68+
<style scoped>
69+
/* RPG 对话框主容器 */
70+
.dialogue-card {
71+
display: flex;
72+
align-items: flex-start;
73+
gap: 12px;
74+
padding: 12px;
75+
background: var(--color-page-bg);
76+
}
77+
78+
/* 头像框样式 */
79+
.ai-avatar {
80+
flex-shrink: 0;
81+
overflow: hidden;
82+
}
83+
84+
.avatar-img {
85+
width: 90px;
86+
height: 90px;
87+
object-fit: cover;
88+
display: block;
89+
}
90+
91+
/* 对话内容框 */
92+
.dialogue-bubble {
93+
position: relative;
94+
flex: 1;
95+
min-width: 0;
96+
padding: 5px 0;
97+
font-size: 0.9rem;
98+
line-height: 1.6;
99+
word-break: break-word;
100+
white-space: pre-wrap;
101+
}
102+
</style>

src/components/Sidebar.vue

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
<template>
22
<aside class="sidebar">
3+
<DragonDialogue
4+
:text="dialogueText"
5+
:dialogue-key="dialogueKey"
6+
:is-ai-enabled="gameMode === 'ai'"
7+
/>
8+
39
<ChessClock :is-clock-enabled="isClockEnabled" :white-time-seconds="whiteTimeSeconds"
410
:black-time-seconds="blackTimeSeconds" :active-color="activeColor" :has-game-started="hasGameStarted" :test-id="clockTestId" />
511

@@ -15,7 +21,7 @@
1521
</div>
1622

1723
<div
18-
class="card game-status"
24+
class="card game-status"
1925
:class="{ 'turn-black': currentTurn === 'black', 'turn-white': currentTurn === 'white' }"
2026
>
2127
<div v-if="gameStatus" class="status-message">{{ gameStatus }}</div>
@@ -76,6 +82,7 @@
7682
import { computed, ref } from 'vue'
7783
import type { Board, Color, PieceType } from '../models/chess'
7884
import ChessClock from './ChessClock.vue'
85+
import DragonDialogue from './DragonDialogue.vue'
7986
import homeSvg from '../assets/icon/home.svg?raw'
8087
import refreshSvg from '../assets/icon/refresh.svg?raw'
8188
import exportPgnSvg from '../assets/icon/export_pgn.svg?raw'
@@ -103,6 +110,8 @@ interface Props {
103110
clockTestId?: string
104111
hasGameStarted?: boolean
105112
gameMode?: 'ai' | 'human' | 'remote'
113+
dialogueText?: string
114+
dialogueKey?: number
106115
}
107116
108117
interface MovePair {
@@ -128,6 +137,8 @@ const props = withDefaults(defineProps<Props>(), {
128137
clockTestId: 'sidebar-chess-clock',
129138
hasGameStarted: false,
130139
gameMode: 'human',
140+
dialogueText: '',
141+
dialogueKey: 0,
131142
})
132143
133144
const emit = defineEmits<{
@@ -413,7 +424,7 @@ const materialDiffText = computed(() => {
413424
gap: 1rem;
414425
padding: 1rem;
415426
width: 100%;
416-
max-width: 300px;
427+
max-width: 350px;
417428
color: var(--color-text-primary);
418429
}
419430

src/composables/useBoardDisplay.ts

Lines changed: 46 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -34,71 +34,60 @@ export function useBoardDisplay() {
3434
}
3535

3636
// --- Overlay 纹理:用于高亮选中格子、合法走法、premove、上一步移动 ---
37-
const getOverlayTexture = (
38-
board: Board,
39-
selectedSquare: { row: number; col: number } | null,
40-
possibleMoves: { row: number; col: number }[],
41-
isDragging: boolean,
42-
hoverSquare: { row: number; col: number } | null,
43-
row: number,
44-
col: number,
45-
premove?: { from: { row: number; col: number }; to: { row: number; col: number } } | null,
46-
lastMove?: { from: { row: number; col: number }; to: { row: number; col: number } } | null,
47-
canPremove?: boolean,
48-
): string | null =>{
49-
// 上一步移动的高亮(起始格和目标格),优先于其他高亮
50-
if (lastMove) {
51-
if (
52-
(lastMove.from.row === row && lastMove.from.col === col) ||
53-
(lastMove.to.row === row && lastMove.to.col === col)
54-
) {
55-
return boardMoveHover
56-
}
57-
}
58-
59-
// Premove 高亮 - 目标格
60-
if (premove && premove.to.row === row && premove.to.col === col) {
61-
return boardPremoveHighlighted
62-
}
63-
64-
// Premove 高亮 - 起始格
65-
if (premove && premove.from.row === row && premove.from.col === col) {
66-
return canPremove
67-
? boardPremoveHover
68-
: boardMoveHover
37+
const getOverlayTexture = (
38+
board: Board,
39+
selectedSquare: { row: number; col: number } | null,
40+
possibleMoves: { row: number; col: number }[],
41+
isDragging: boolean,
42+
hoverSquare: { row: number; col: number } | null,
43+
row: number,
44+
col: number,
45+
premove?: { from: { row: number; col: number }; to: { row: number; col: number } } | null,
46+
lastMove?: { from: { row: number; col: number }; to: { row: number; col: number } } | null,
47+
canPremove?: boolean,
48+
): string | null => {
49+
const move = possibleMoves.find(
50+
(candidate) => candidate.row === row && candidate.col === col,
51+
)
52+
const targetPiece = board[row]?.[col] ?? null
53+
const isCapture = move !== undefined && targetPiece !== null
54+
55+
if (isCapture) {
56+
if (hoverSquare?.row === row && hoverSquare?.col === col) {
57+
return canPremove ? boardPremoveHover : boardMoveHover
6958
}
59+
return canPremove ? boardPremoveCapture : boardMoveCapture
60+
}
7061

71-
// 当前选中格高亮(premove 模式下使用 premove 版纹理)
72-
if (selectedSquare?.row === row && selectedSquare?.col === col) {
73-
return canPremove
74-
? boardPremoveHover
75-
: boardMoveHover
76-
}
62+
if (premove && premove.to.row === row && premove.to.col === col) {
63+
return boardPremoveHighlighted
64+
}
7765

78-
const move = possibleMoves.find(
79-
(candidate) => candidate.row === row && candidate.col === col,
80-
)
66+
if (premove && premove.from.row === row && premove.from.col === col) {
67+
return canPremove ? boardPremoveHover : boardMoveHover
68+
}
8169

82-
if (move) {
83-
if (hoverSquare?.row === row && hoverSquare?.col === col) {
84-
return canPremove
85-
? boardPremoveHover
86-
: boardMoveHover
87-
}
70+
if (selectedSquare?.row === row && selectedSquare?.col === col) {
71+
return canPremove ? boardPremoveHover : boardMoveHover
72+
}
8873

89-
const targetPiece = board[row]?.[col] ?? null
90-
if (targetPiece !== null) {
91-
return canPremove
92-
? boardPremoveCapture
93-
: boardMoveCapture
94-
}
95-
return canPremove
96-
? boardPremovePlaceable
97-
: boardMovePlaceable
74+
if (move) {
75+
if (hoverSquare?.row === row && hoverSquare?.col === col) {
76+
return canPremove ? boardPremoveHover : boardMoveHover
9877
}
78+
return canPremove ? boardPremovePlaceable : boardMovePlaceable
79+
}
9980

100-
return null
81+
if (lastMove) {
82+
if (
83+
(lastMove.from.row === row && lastMove.from.col === col) ||
84+
(lastMove.to.row === row && lastMove.to.col === col)
85+
) {
86+
return boardMoveHover
87+
}
10188
}
89+
return null
90+
}
10291

10392
// --- 棋子图片 ---
10493
const getPieceImage = (

0 commit comments

Comments
 (0)