this.wordDb = {
'사': ['사과', '사자', '사슴'],
'과': ['과자', '과일', '과하주'],
'기': ['기차', '기린', '기쁨'],
'차': ['차이쨔', '차표', '차량'],
'학': ['학교', '학생', '학원'],
'교': ['교문', '교복', '교사'],
'이': ['이발', '이유', '이글루']
};
// 2~5수 뒤 반드시 이기는 한방 단어 및 유도 단어 정의
this.winningWords = ['차이쨔', '과하주', '즙'];
}
resetGame() {
this.isGameActive = false;
this.isFirstTurn = true;
this.usedWords = new Set();
this.lastLetter = '';
}
// 단어가 사전에 존재하는지 확인하는 가상 함수
isValidDictionaryWord(word) {
const firstChar = word.charAt(0);
return this.wordDb[firstChar] && this.wordDb[firstChar].includes(word);
}
// 단어에서 다음 첫 글자 추출 (두음법칙 필요 시 여기를 수정)
getLastChar(word) {
return word.charAt(word.length - 1);
}
// AI가 최선의 단어를 선택하는 로직
getBestAiWord(startChar) {
const candidates = this.wordDb[startChar] || [];
// 사용 가능한 단어 필터링 (이미 사용한 단어 제외)
const available = candidates.filter(w => !this.usedWords.has(w));
if (available.length === 0) return null;
// 1. 상대방이 지는 유도수를 두었을 때 처리 (가장 강한 한방 단어 우선 탐색)
for (const word of available) {
if (this.winningWords.includes(word)) {
return word;
}
}
// 2. 자신이 지지 않는 단어 중 무작위 혹은 첫 번째 단어 선택
return available[0];
}
// 사용자의 입력을 처리하는 메인 함수
processInput(input) {
const trimmed = input.trim();
// 시스템 명령어 처리
if (trimmed === '!시작' || trimmed === '네' || trimmed === '다시') {
this.resetGame();
this.isGameActive = true;
return "안녕하세요? 끝말잇기 봇입니다.\n님부터 먼저 하실래요, 저부터 먼저 하실래요?";
}
// 게임 시작 전 예외 처리
if (!this.isGameActive) {
if (trimmed === '님부터' || trimmed === '저부터') {
this.isGameActive = true;
if (trimmed === '님부터') {
// AI가 먼저 시작
const startChars = Object.keys(this.wordDb);
const randomChar = startChars[Math.floor(Math.random() * startChars.length)];
const aiWord = this.getBestAiWord(randomChar);
this.isFirstTurn = false;
this.usedWords.add(aiWord);
this.lastLetter = this.getLastChar(aiWord);
return `/${aiWord}`;
}
return "네, 먼저 시작해 주세요.";
}
return "끝말잇기 게임 중입니다.\n!시작을 입력하여 게임을 시작해주세요.";
}
// 슬래시(/) 명령어 형식 검증
if (!trimmed.startsWith('/')) {
return "명령어를 읽지 못하였습니다. /(단어)로 말해주시길 바랍니다.";
}
const userWord = trimmed.substring(1);
// 첫 턴 한방/유도 단어 제한 검증
if (this.isFirstTurn && this.winningWords.includes(userWord)) {
return "한방 / 유도는 처음에 불가능 합니다.";
}
// 끝말잇기 규칙 검증 (첫 턴이 아닐 때)
if (!this.isFirstTurn && userWord.charAt(0) !== this.lastLetter) {
return `이전 단어의 마지막 글자인 '${this.lastLetter}'로 시작해야 합니다.`;
}
// 이어말하기 꼼수 방지 (예: 이삭 -> 이삭토스트 금지)
if (!this.isFirstTurn && userWord.includes(this.lastLetter + userWord.substring(1))) {
// 실제 구현상 엄격한 포함 관계는 기획에 따라 조율 가능
}
// 이미 사용한 단어인지 검증
if (this.usedWords.has(userWord)) {
return "이미 사용한 단어입니다.";
}
// 존재하지 않는 단어 검증
if (!this.isValidDictionaryWord(userWord)) {
return "존재하지 않는 단어입니다.";
}
// 사용자의 정당한 수 수락
this.usedWords.add(userWord);
this.isFirstTurn = false;
const nextStartChar = this.getLastChar(userWord);
// AI의 턴 계산
const aiResponseWord = this.getBestAiWord(nextStartChar);
// AI가 더 이상 이을 단어가 없는 경우 (플레이어 승리)
if (!aiResponseWord) {
this.isGameActive = false;
return "어? 제가 졌네요.\n다시 하실래요?";
}
// AI의 수 확정
this.usedWords.add(aiResponseWord);
this.lastLetter = this.getLastChar(aiResponseWord);
// AI가 한방 단어를 내서 플레이어가 다음에 이을 단어가 없는지 선제 체크
const playerNextCandidates = this.wordDb[this.lastLetter] || [];
const playerAvailable = playerNextCandidates.filter(w => !this.usedWords.has(w));
if (playerAvailable.length === 0) {
this.isGameActive = false;
return `/${aiResponseWord}\n\n어, 제가 이겼네요.\n다시 하실래요?`;
}
return `/${aiResponseWord}`;
}
}