250x250
Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

y.developer

[TIL] Day 71 실시간 채팅에 따른 호감도 알고리즘 구현 본문

카테고리 없음

[TIL] Day 71 실시간 채팅에 따른 호감도 알고리즘 구현

y.developer 2024. 1. 16. 02:04
728x90

2024.01.15 월

 

Crosswalk : 소울메이트 매칭 서비스

소울메이트 매칭 서비스를 제작하고 있다.

프로젝트의 핵심 기능 중 하나가 실시간 채팅이다.

A와 B 두사람이 매칭되고, 서로 제한된 정보만을 가지고 실시간 채팅을 통해서 서로에 대해서 알아간다.

채팅이 이어짐에 따라서 호감도가 점점 올라가고, 100%에 도달 했을 때 모든 정보가 오픈되고 최종 매칭에 성공하게 된다.

 

 

호감도가 올라가는 조건

큰 구조는 잡혔다. 이제 호감도를 어떤 조건으로 올리게 만들건지 정해야한다.

제한된 정보가 주어지며 서로 대화를 통해서 알아가야 한다.

이 취지에 맞추자면 핵심은 두 사람간의 티.키.타.카

서로 이야기를 계속해서 주고 받으며 대화가 활성화되도록 유도해야 한다.

따라서 호감도의 핵심 요소는 채팅 메시지 수를 기준을 잡았다.

 

 

한 사람이 모든 호감도를 올린다면...

사람의 마음은 언제나 상대적인 것이다. 모든 것에 아주 정확한 균형은 없다.

한 사람이 너무 앞서가거나, 한 사람이 너무 늦게 따라가면 호감이라고 볼 수 없다.

이를 방지하기 위해서 몇가지 조건을 추가했다.

 

1. 한 사람이 연속으로 올릴 수 있는 호감도 기여도를 제한하자.

한 사람이 모든 기여도를 한 번에 올리는 것을 방지하기 위해서 연속적인 채팅수에 대해서 제한을 걸었다.

A라는 사람이 연속된 채팅을 3개 쓴다면 OK 호감도에 반영해준다. 하지만 4개 이상을 연속적으로 적는다면 이는 호감도에 영향을 주지 않는다. 즉, 연속된 채팅수가 3개가 넘어 간다면 아무런 변화가 없는 것이다.

 

2. 상대방이 채팅을 보내는 순간 연속채팅수 제한이 해제된다.

상대방(B)이 채팅을 보낼 때 비로소 A의 연속 채팅수 제한이 리셋이 되고 다시 연속된 채팅수 3개까지는 호감도에 기여할 수 있다. 이는 서로 쌍방으로 조건이 발동된다.

 

 

주목적은 티키타카

이로써 티키타카를 해야만! 호감도에 기여할 수 있도록 시스템을 구축했다.

호감도 %는 상단에 통합하여 표시되며, 대화를 이어갈수록 점차 높아지는 호감도를 통해 서로에 대한 마음을 확인 할 수 있다. 

 

 

알고리즘 구현

테스트 페이지를 만들고 간단한 UI와 채팅과 같은 시스템을 구축한 후 테스트를 진행했다.

머릿속으로만 생각했던 로직을 구현해보고 실제로 유저의 경험이 어떤지 확인해보았다.

 

직접 구현해보니 생각보다 더 간단했다.

처음 구상한 대호 알고리즘을 구현하면 될 것 같고,

호감도 목표치를 테스트 해보고 난이도롤 조절하면 될 것 같다.

유저가 총 몇번의 대화를 나누길 원하는지, 호감도 100%에 도달하는 기간을 어느 정도로 설정할 것인지 생각해야겠다.

그리고 호감도 100%가 되었을 때 어떻게 축하메시지를 임펙트 있게 전달하여 최종 매칭을 축하할지 고민중이다.

 

우리 서비스의 핵심 키워드인 Crosswalk, 신호등, 초록불에 맞춰 우리만의 아이덴티티를 잘 녹여볼 생각이다.

참고로 초기 대화방만 열렸을 땐 노란불, 매칭이 실패했을 땐 빨간불, 최종 매칭이 성공했을 땐 초록불을 밝혀줄 것이다.

 

 

협업을 위해서 주석으로 설명

구두로 설명을 진행하고 로직을 구축하긴 했지만, 실제 시스템에 적용하기 위해서는 약간의 로직 변화가 있을 것이다.

협업을 진행하고 있기 때문에 알고리즘에 대한 이해도와 내가 의도한 바를 정확히 전달할 수 있도록 주석을 통해 단계별로 로직에 대한 설명을 덧붙였다.

 

 

 

'use client';
import React, { useEffect, useState } from 'react';

function FavorableRatingAlgorithms() {
  const [favorableRating, setFavorableRating] = useState<number>(0); //호감도
  const [congratulationsMessage, setCongratulationsMessage] = useState<boolean>(false); //호감도 100% 달성시 축하메시지 토글
  const [totalChatCount, setTotalChatCount] = useState<number>(0); //A와 B의 총 채팅 수

  const [AScore, setAScore] = useState<number>(0); //A의 총 기여도(점수)
  const [AContinualCount, setAContinualCount] = useState<number>(0); //A의 연속된 채팅 수
  const [AChat, setAChat] = useState<string[]>(['log']); //A의 채팅

  const [BScore, setBScore] = useState<number>(0); //B의 총 기여도(점수)
  const [BContinualCount, setBContinualCount] = useState<number>(0); //B의 연속된 채팅 수
  const [BChat, setBChat] = useState<string[]>(['log']); //B의 채팅

  /**호감도 난이도 */
  const favorableRatingGoal = 100; //호감도 목표치

  /**A 채팅 전송 버튼 */
  const handleAChatBtn = () => {
    setBContinualCount(0); // B(상대)의 연속된 채팅 수 리셋
    // 연속된 채팅 수가 3이상일 때 기여도에 반영 X (한사람만 기여도를 계속 돌리는 것을 방지)
    if (AContinualCount < 3) {
      setAScore(AScore + 1); // 총 기여도 +1
      increaseFavorableRating(AScore + BScore + 1); // A 기여도 + B 기여도 합 + 1 값을 전달하여 호감도 계산
    }
    setAContinualCount(AContinualCount + 1); // 연속된 채팅 수 +1
    setTotalChatCount(totalChatCount + 1); // A와 B의 총 채팅 수 +1
    setAChat([...AChat, `A${AScore}`]); // 채팅 내용 전송
  };

  /**B 채팅 전송 버튼 */
  const handleBChatBtn = () => {
    setAContinualCount(0);
    if (BContinualCount >= 3 && AContinualCount === 0) {
    } else {
      setBScore(BScore + 1);
      increaseFavorableRating(AScore + BScore + 1);
    }
    setBContinualCount(BContinualCount + 1);
    setTotalChatCount(totalChatCount + 1);
    setBChat([...BChat, `B${BScore}`]);
  };

  /**호감도 % 계산 및 100% 달성시 축하메시지 상태 true로 변경 */
  const increaseFavorableRating = (score: number) => {
    const rating = (score / favorableRatingGoal) * 100;
    if (rating >= 100) {
      return setCongratulationsMessage(true);
    }
    setFavorableRating(Math.floor(rating));
  };

  useEffect(() => {
    // 현재 호감도 % 확인
    console.log('rating : ', Math.floor(favorableRating), '%');
  }, [favorableRating]);

  return (
    <div className="flex flex-col gap-12">
      {congratulationsMessage === false ? (
        <>
          <div className="flex justify-center font-bold text-5xl">
            호감도 : <span className="text-red-500">{favorableRating}</span>%
          </div>
          <div className="flex justify-center gap-4">
            <button //호감도 100% 버튼
              onClick={() => {
                setFavorableRating(100);
                setCongratulationsMessage(true);
              }}
            >
              100%
            </button>
            <button //호감도 95% 버튼
              onClick={() => {
                setFavorableRating(95);
                setAScore(50);
                setBScore(45);
              }}
            >
              95%
            </button>
          </div>
        </>
      ) : (
        <h1 className="flex justify-center text-center text-3xl font-bold text-green-600">
          축하해요! <br /> 초록불이 켜졌습니다!!
        </h1>
      )}

      <div className="flex flex-col gap-2 p-4 rounded-xl border-2 border-red-500">
        <div>A 기여도(점수) : {AScore}</div>
        <div>A 연속 채팅수 : {AContinualCount}</div>
        <div>B 기여도(점수) : {BScore}</div>
        <div>B 연속 채팅수 : {BContinualCount}</div>
        <div>채팅수 총합 : {totalChatCount}</div>
        <div className="flex gap-2 w-full">
          <button className="w-1/2 rounded-xl border-2 border-green-500 bg-green-300" onClick={handleAChatBtn}>
            A
          </button>
          <button className="w-1/2 rounded-xl border-2 border-blue-500 bg-blue-300" onClick={handleBChatBtn}>
            B
          </button>
        </div>
      </div>
      <div>
        <div className="flex gap-6 p-4">
          <div className="flex flex-col items-center gap-2 w-[10rem] rounded-xl border-2 border-green-500 p-2">
            {AChat.map((a, index) => {
              return <div key={index}>{a}</div>;
            })}
          </div>
          <div className="flex flex-col items-center gap-2 w-[10rem] rounded-xl border-2 border-blue-500 p-2">
            {BChat.map((b, index) => {
              return <div key={index}>{b}</div>;
            })}
          </div>
        </div>
      </div>
    </div>
  );
}

export default FavorableRatingAlgorithms;

 

 

 

 

 

728x90