00. 개발완료보고서
제출일 : 2025.08.22(금)
|
개발 완료 보고서
|
||||
|
프로젝트
|
라이어 게임
|
|||
|
개발인원
|
이명진
|
|||
|
활동일시
|
25.08.14 ~ 25.08.22
|
장소
|
공학 1관 드론융합실
|
|
|
주요주제
|
상대방을 속여 최후의 승자가 되거나 협력하여 공동의 승리를 할 수 있는 멀티플레이어 심리 게임 구현
|
|||
|
개발 환경
|
- OS : Ubuntu 24.04.2 LTS
- Language : C++
- IDE : Qt Creator, MySQL
|
|||
|
구현기능
|
- 회원가입 : 사용자로부터 아이디, 비밀번호, 닉네임 등의 정보를 받아 MySQL 데이터베이스에 안전하게 저장하고, 비밀번호는 암호화하여 저장함.
- 로그인: 사용자가 입력한 아이디와 비밀번호를 데이터베이스의 정보와 비교하여 인증을 처리.
- 회원정보 찾기 (아이디/비밀번호 찾기): 사용자가 등록한 이메일 주소 등을 통해 아이디를 찾거나, 비밀번호를 재설정할 수 있는 기능을 제공.
- 닉네임 변경 : 로그인한 사용자가 닉네임을 수정할 수 있도록 함.
- 비밀번호 변경 : 로그인한 사용자가 비밀번호를 수정할 수 있도록 함.
- 방 생성 : 사용자가 직접 게임방을 만들 수 있도록 함.
- 방 입장 : 사용자가 생성된 방에 참여할 수 있도록 함.
- 방 삭제 : 자신이 생성한 방을 삭제할 수 있도록
- 일반 채팅: 게임 시작 전, 대기실이나 로비에서 플레이어들이 자유롭게 대화할 수 있는 채팅 기능을 제공.
- 카테고리 : 게임 시작 전, 방장이 카테고리를 선택.
- 라이어 선정 : 랜덤하게 라이어를 선정하여 출력함.
- 단어 분배 : 서버는 선택된 카테고리에서 무작위로 정답 단어를 선정.
'라이어'를 제외한 모든 플레이어에게는 정답 단어를 알려줌.
- 게임 채팅: 게임 시작 후, 라운드당 한 번씩만 채팅할 수 있도록 제한하여 무분별한 힌트 노출을 방지하고 심리전을 강화.
- 라이어 정답 : 모든 라운드 완료 후 라이어가 정답을 말할 수 있도록 함.
- 투표 : 라이어가 정답을 맞추지 못하면 모든 사람들이 투표할 수 있도록 함.
- 게임종료 : 라이어의 투표수가 많으면 시민의 승리로, 그렇지 않으면 라이어의 승리를 출력함.
그 외 투표가 동률일 때에는 무승부로 라운드를 한번 더 진행함.
|
|||
|
예상문제점
|
- 네트워 및 동기화 문제 : 멀티플레이어 게임 특성상 네트워크 지연이 발생하면 게임 진행에 문제가 생길 수 있음.
- 공정성 문제 : 승패에 영향을 주는 중요한 데이터가 클라이언트에서 조작될 경우, 게임의 공정성이 무너질 수 있음.
- 게임 밸런스 : 게임 규칙이 너무 복잡하거나, 특정 전략이 너무 강력하면 게임의 재미가 반감될 수 있음.
|
|||
|
해결방법
|
- 클라이언트에서 중복 입력으로 서버의 부담을 주어 네트워크 지연 발생을 염려하여 버튼 한번 클릭 후에는 버튼을 비활성화하고, 서버에서 응답을 받을 때만 버튼을 활성화시키는 방식으로 최대한 방지함.
- 승패에 영향을 줄 수 있는 처리는 모두 서버에서 해결하고, 단순 행동에 대한 요청만 클라이언트에서 담당하였음.
- 라이어게임의 기존 규칙을 최대한 바꾸지 않도록 하였고, 3라운드라는 비교적 짧은 시간으로 게임을 끝낼 수 있도록 하여 게임 규칙을 간단히 함.
|
|||
|
요구사항 분석서 체크리스트
|
1. 참조
|
|||
|
수정문서
|
2. 참조
|
|||
|
구현 스크린샷 및 기능 설명
|
3. 참조
|
|||
|
실행 영상
|
4. 참조
|
|||
|
소스코드
|
5. 참조
|
|||
|
개인 개발 후기
|
6. 참조
|
|||
01. 요구사항 분석서 체크리스트
|
기능적 요구사항
|
|||||
|
ID
|
기능명
|
세부내용
|
UC
|
성공여부
|
설명
|
|
FR01
|
회원가입
|
- 아이디, 비밀번호, 닉네임으로 회원가입할 수 있어야 함.
- 비밀번호는 암호화되어야 함.
|
UC01
|
O
|
로그인 페이지에서
가능
|
|
FR02
|
로그인
|
- 아이디와 비밀번호를 입력하여 로그인할 수 있어야 함.
|
UC02
|
O
|
로그인 페이지에서
가능
|
|
FR03
|
회원정보 찾기
|
등록된 이메일 등을 통해 아이디를 찾거나 비밀번호를 재설정할 수 있어야 함.
|
UC03
|
O
|
로그인 페이지에서
가능
|
|
FR04
|
회원정보 변경
|
로그인 후 비밀번호와 닉네임을 변경할 수 있어야 함.
|
UC04
|
O
|
메인 페이지에서 가능
|
|
FR05
|
게임방
생성
|
게임 제목, 최대 인원 등을 설정하여 새로운 게임방을 만들 수 있어야 함.
|
UC05
|
O
|
메인 페이지에서 가능
|
|
FR06
|
게임방
입장
|
생성된 게임방 목록을 보고 원하는 방에 입장할 수 있어야 함.
|
UC06
|
O
|
메인 페이지에서 가능
|
|
FR07
|
일반 채팅
|
게임 시작 전 대기실에서 자유롭게 채팅할 수 있어야 함.
|
UC07
|
O
|
게임 로비 페이지에서 가능
|
|
FR08
|
게임 시작
|
방장이 게임을 시작할 수 있어야 함.
|
UC08
|
O
|
게임 로비 페이지에서 가능
|
|
FR09
|
게임 채팅
|
게임 시작 후에는 라운드당 한 번씩만 채팅할 수 있어야 함.
|
UC09
|
O
|
게임 페이지에서 가능
|
|
FR10
|
카테고리 선택
|
게임 시작 후 각 라운드마다 참여자들이 게임의 카테고리를 선택할 수 있어야 함.
|
UC10
|
O
|
게임 페이지에서 가능
|
|
FR11
|
단어 분배
|
선택된 카테고리에서 정답 단어와 라이어 단어를 선정하여 각 플레이어에게 다르게 분배해야 함.
|
UC11
|
O
|
게임 페이지에서 가능
|
|
FR12
|
단어 확인
|
'라이어'를 제외한 모든 플레이어는 정답 단어를, '라이어'는 다른 단어를 화면에서 확인할 수 있어야 함.
|
UC12
|
O
|
게임 페이지에서 가능
|
|
FR13
|
투표
|
라운드 종료 후, 플레이어들이 '라이어'라고 생각하는 사람에게 투표할 수 있어야 함.
|
UC13
|
O
|
게임 페이지에서 가능
|
|
FR14
|
승패결정
|
투표 결과 등 게임 규칙에 따라 '라이어'를 찾아내거나 '라이어'가 승리하는 등의 최종 승패를 결정해야 함.
|
UC14
|
O
|
게임 페이지에서 가능
|
|
FR15
|
게임 상태
동기화
|
시스템은 모든 플레이어의 게임 상태(턴, 남은 시간, 채팅 내용 등)를 실시간으로 동기화해야 함.
|
UC15
|
O
|
전체적 관리
|
|
비기능적 요구사항
|
||||
|
ID
|
유형
|
세부내용
|
성공
여부
|
설명
|
|
NFR01
|
성능
|
C++와 MySQL을 활용하여 10명 이내의 플레이어가 참여하는 게임 환경에서 딜레이 없이 원활하게 작동해야 함.
|
△
|
총 10명의 플레이어가 게임에 참여가능. 간헐적 끊김 발생.
|
|
NFR02
|
보안
|
비밀번호는 암호화하여 저장해야 하며, 회원정보 찾기/변경 시 강력한 본인 인증 절차를 거쳐야 한다.
|
O
|
비밀번호 암호화, 각각 아이디 및 이메일을 요구함.
|
|
NFR03
|
안정성
|
서버 오류 발생 시 데이터 유실을 최소화하는 방안을 고려해야 함.
|
X
|
서버 끊김 발생 시 DB의 저장 내용을 초기화하지 못함.
|
|
NFR04
|
사용성
|
Qt Creator로 개발된 UI는 직관적이며 사용하기 편리해야 함.
|
O
|
이미지를 통해 시각적인 디자인 개선, 버튼과 Edit 같은 UI는 직관적으로 배치함.
|
|
NFR05
|
호환성
|
Ubuntu 24.04.2 LTS 개발 환경에서 안정적으로 동작해야 함.
|
O
|
안정적으로 동작함.
|
02. 수정문서
없음
03. 구현 스크린샷 및 설명
1. 서버와 로그인 화면
>
로그인, 회원가입, 아이디 찾기, 비밀번호 찾기 선택

1.2 로그인 화면
> 아이디, 비밀번호 입력후 로그인 가능

1.3 회원가입 화면
> 각 입력창 입력후 회원가입 가능

1.4 아이디 찾기 화면
> 이메일 입력 시 아이디 찾기 가능

1.5 비밀번호 찾기 화면(실제기능은 비밀번호 변경)
> 각 입력창 입력 후 비밀번호 변경 가능

2. 메인화면
> 닉네임 표시, 설정, 방만들기, 방 삭제, 입장 가능

2.1 닉네임 변경, 비밀번호 변경, 로그아웃 가능

2.2 닉네임 변경
> 변경할 닉네임 입력후 변경 가능

2.3 비밀번호 변경
> 이전 비밀번호와 변경 비밀번호 입력 후 변경 가능

2.4 로그아웃

2.5 방 만들기
> 방 이름 입력 후 방 만들기 가능

2.6 방 삭제
> 해당 방의 방장만 삭제 가능


2.7 방 입장
> 입장 버튼으로 방 입장 가능

3. 게임 로비 화면
> 닉네임 표시, 일반채팅, 유저 입장 목록


3.1 카테고리 선택
> 게임시작 버튼 클릭 후 카테고리 선택 가능

3.2 게임시작
> 방장만이 게임을 시작할 수 있음


4. 게임화면
> 닉네임 표시, 직업 표시, 일반채팅, 게임채팅, 시스템메시지


4.1 게임로직1
> 해당 인원들이 1라운드마다 게임채팅으로 힌트를 제공

4.2 게임로직2
> 모든 라운드 진행 후 라이어는 정답을 입력
> 맞췄다면 라이어의 승리, 틀렸다면 투표를 진행

4.3 게임로직3
> 투표를 진행하여 라이어가 가장 많은 득표를 하면 시민의 승리, 무승부의 경우 라운드를 재시작, 그 외의 경우에는 라이어의 승리


4.4 게임로직4
> 최종 결과를 출력하고 게임 상태 초기화 후 해당 방에 있는 클라이언트들의 화면을 게임로비화면으로 전환

04. 실행영상
동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.
05. 전체 소스코드 및 주요코드
1. 서버-DB 연결
// 버튼과 서버시작 연결
void MainWindow::on_serverStartButton_clicked()
{
// 서버가 현재 실행 중인지 확인
if (tcpServer->isListening()) {
// 서버가 실행 중이면 종료 로직 수행
tcpServer->close();
ui->logTextEdit->append("서버가 종료되었습니다.");
ui->serverStatusLabel->setText("서버 상태: <font color='red'>종료됨</font>");
ui->serverStartButton->setText("서버 시작");
ui->dbStatusLabel->setText("DB 연결 상태: <font color='gray'>끊김</font>");
if (db.isOpen()) {
db.close();
}
} else {
// 서버가 종료 상태면 시작 로직 수행
// 1. DB 연결
if (!connectDatabase()) {
ui->logTextEdit->append("DB 연결 실패로 인해 서버를 시작할 수 없습니다.");
QMessageBox::critical(this, "서버 시작 오류", "DB 연결에 실패했습니다.");
return;
}
// 2. DB 연결 성공 시, 서버 시작
if (tcpServer->listen(QHostAddress::Any, 9806)) {
ui->logTextEdit->append("서버 시작 성공! 포트 9806에서 대기 중.");
ui->serverStatusLabel->setText("서버 상태: <font color='green'>실행 중</font>");
ui->serverStartButton->setText("서버 중지");
} else {
QString errorMsg = "서버 시작 실패: " + tcpServer->errorString();
ui->logTextEdit->append(errorMsg);
ui->serverStatusLabel->setText("서버 상태: <font color='red'>실패</font>");
QMessageBox::critical(this, "서버 시작 오류", errorMsg);
}
}
} // on_serverStartButton_clicked()
// DB 연결
bool MainWindow::connectDatabase()
{
// DB 연결 정보를 반복적으로 설정하지 않도록 한 번만 설정
if (db.isOpen()) {
db.close();
}
db.setHostName(DB_HOST_NAME);
db.setDatabaseName(DB_DATABASE_NAME);
db.setUserName(DB_USER_NAME);
db.setPassword(DB_PASSWORD);
if (db.open()) {
ui->logTextEdit->append("DB 연결 성공!");
ui->dbStatusLabel->setText("DB 연결 상태: <font color='green'>성공</font>");
return true;
} else {
ui->logTextEdit->append("DB 연결 실패: " + db.lastError().text());
ui->dbStatusLabel->setText("DB 연결 상태: <font color='red'>실패</font>");
return false;
}
} // connectDatabase()
1.1 클라이언트-서버 연결
<서버>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// TCP 서버 객체 생성 및 시그널 연결
tcpServer = new QTcpServer(this);
connect(tcpServer, &QTcpServer::newConnection, this, &MainWindow::handleNewConnection);
// DB 객체 초기화
db = QSqlDatabase::addDatabase("QMYSQL");
} // MainWindow()
void MainWindow::handleNewConnection()
{
// 새로운 클라이언트 소켓을 가져와서 리스트에 추가
QTcpSocket* newClientSocket = tcpServer->nextPendingConnection();
if (newClientSocket) {
clientSockets.append(newClientSocket);
ui->logTextEdit->append("새로운 클라이언트 접속: " + newClientSocket->peerAddress().toString());
// 소켓의 시그널들을 슬롯에 연결
connect(newClientSocket, &QTcpSocket::readyRead, this, &MainWindow::handleSocketReadyRead);
connect(newClientSocket, &QTcpSocket::disconnected, this, &MainWindow::handleSocketDisconnected);
// 새로운 클라이언트의 패킷 관련 정보 초기화
clientDataBuffers[newClientSocket] = QByteArray();
clientNextBlockSizes[newClientSocket] = 0;
// 로그에 연결 정보 기록
qDebug() << "새로운 클라이언트 연결:" << newClientSocket->peerAddress().toString();
// 새로운 클라이언트가 연결되자마자 DB에 있는 방 목록을 전송
sendRoomListToAllClients();
}
} // handleNewConnection()
<클라이언트>
// 소켓 시그널-슬롯 연결
connect(socket, &QTcpSocket::connected, this, &MainWindow::handleConnected);
// 연결
void MainWindow::handleConnected()
{
qDebug() << "서버에 연결되었습니다.";
}
2. 서버와 클라이언트 간 데이터 전송
<서버>
// 클라이언트의 전송 데이터 관리
void MainWindow::handleSocketReadyRead()
{
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
// 소켓과 연결된 QDataStream을 생성합니다.
QDataStream in(socket);
in.setVersion(QDataStream::Qt_6_0);
// 루프를 사용하여 소켓에 남은 모든 데이터 패킷을 처리합니다.
while (socket->bytesAvailable() > 0) {
quint32 nextBlockSize = 0;
// 패킷 크기(4바이트)가 충분히 도착했는지 확인합니다.
if (socket->bytesAvailable() < (int)sizeof(quint32)) {
return;
}
// 패킷 크기를 읽고, 스트림의 위치를 다음 데이터로 이동시킵니다.
in >> nextBlockSize;
// 패킷 본문이 완전히 도착했는지 확인합니다.
if (socket->bytesAvailable() < nextBlockSize) {
return;
}
// 패킷 본문 데이터를 읽습니다.
QByteArray jsonData;
in >> jsonData;
// JSON 데이터 처리
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError);
if (!doc.isNull() && doc.isObject() && parseError.error == QJsonParseError::NoError) {
HandleRequestByClient(socket, doc.object());
} else {
ui->logTextEdit->append("잘못된 JSON 형식의 요청 수신");
qDebug() << "파싱 오류: " << parseError.errorString();
}
}
} // handleSocketReadyRead()
// 클라이언트로부터 요청
void MainWindow::HandleRequestByClient(QTcpSocket* socket, const QJsonObject& request)
{
// JSON 객체에서 "type" 키를 읽음.
QString requestType = request["type"].toString();
// 로그에 요청 타입과 클라이언트 주소 기록
ui->logTextEdit->append(QString("요청 수신 (%1): %2").arg(socket->peerAddress().toString(), requestType));
// "type" 키와 대응된 값에 따라 처리 함수로 분기
// 클라이언트와 일치해야 함
if (requestType == "login") {
handleLogin(socket, request);
} else if (requestType == "signup") {
handleSignUp(socket, request);
} else if (requestType == "check_id") {
handleCheckId(socket, request);
} else if (requestType == "check_email") {
handleCheckEmail(socket, request);
} else if (requestType == "find_account") {
handleFindAccount(socket, request);
} else if (requestType == "password_change") {
handlePasswordChange(socket, request);
} else if (requestType == "logout") {
handleLogout(socket, request);
} else if (requestType == "change_nickname") {
handleNicknameChange(socket, request);
} else if (requestType == "create_room") {
handleCreateRoom(socket, request);
} else if (requestType == "join_room") {
handleJoinRoom(socket, request);
} else if (requestType == "leave_room") {
handleExitRoom(socket, request);
} else if (requestType == "delete_room") {
handleDeleteRoom(socket, request);
} else if (requestType == "chat") {
handleChat(socket, request);
} else if (requestType == "start_game") {
handleStartGame(socket, request);
} else if (requestType == "hint_message") {
handleHintMessage(socket, request);
} else if (requestType == "liar_answer") {
handleLiarAnswer(socket, request);
} else if (requestType == "vote") {
handleVote(socket, request);
}
}
// 클라이언트에게 전송
void MainWindow::SendResponseToClient(QTcpSocket* socket, const QJsonObject& response)
{
// JSON 객체를 바이트 배열로 변환
QByteArray jsonData = QJsonDocument(response).toJson(QJsonDocument::Compact);
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_6_0);
// 4바이트 헤더를 위한 공간을 확보합니다.
out << (quint32)0;
// JSON 데이터를 직렬화하여 담습니다.
out << jsonData;
// 헤더를 다시 쓰고, 전체 데이터 크기를 업데이트합니다.
out.device()->seek(0);
out << (quint32)(block.size() - sizeof(quint32));
// 데이터 전송
socket->write(block);
socket->flush();
}
<클라이언트>
// 클라이언트의 전송 데이터 관리
void MainWindow::handleSocketReadyRead()
{
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
// 소켓과 연결된 QDataStream을 생성합니다.
QDataStream in(socket);
in.setVersion(QDataStream::Qt_6_0);
// 루프를 사용하여 소켓에 남은 모든 데이터 패킷을 처리합니다.
while (socket->bytesAvailable() > 0) {
quint32 &nextBlockSize = clientNextBlockSizes[socket];
// 패킷 크기 아직 모르면 읽기 시도
if (nextBlockSize == 0) {
if (socket->bytesAvailable() < (int)sizeof(quint32))
break; // 아직 헤더도 안 옴 → 다음 readyRead 때 이어서
in >> nextBlockSize;
}
// 패킷 크기(4바이트)가 충분히 도착했는지 확인합니다.
if (socket->bytesAvailable() < nextBlockSize) {
break;
}
// 패킷 본문 데이터를 읽습니다.
QByteArray jsonData;
in >> jsonData;
// JSON 데이터 처리
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError);
if (!doc.isNull() && doc.isObject() && parseError.error == QJsonParseError::NoError) {
HandleRequestByClient(socket, doc.object());
} else {
ui->logTextEdit->append("잘못된 JSON 형식의 요청 수신");
qDebug() << "파싱 오류: " << parseError.errorString();
}
nextBlockSize = 0;
}
} // handleSocketReadyRead()
// 서버의 요청 응답
void MainWindow::ResponseByServer(const QJsonObject& json)
{
// 응답 처리가 시작되면 isRequestInProgress를 리셋하고 버튼 활성화 함수 호출
isRequestInProgress = false;
setAllButtonsEnabled(true);
QString type = json["type"].toString();
// 회원 정보 관련 응답-------------------------------------------------------------------------
// 회원가입 응답
if (type == "signup_response") {
handleSignupResponse(json);
}
// 아이디 중복검사 응답
else if (type == "check_id_response") {
handleIdCheckResponse(json);
}
// 이메일 중복검사 응답
else if (type == "check_email_response") {
handleEmailCheckResponse(json);
}
// 로그인 응답 처리 로직
else if (type == "login_response") {
handleLoginResponse(json);
}
// 아이디 찾기 응답
else if (type == "find_account_response") {
handleFindAccountResponse(json);
}
// 비밀번호 찾기시 변경 응답
else if (type == "password_change_response") {
handlePasswordChangeResponse(json);
}
// 로그아웃
else if (type == "logout_response") {
handleLogoutResponse(json);
}
// 닉네임 변경 응답
else if (type == "change_nickname_response") {
handleChangeNicknameResponse(json);
}
// 비밀번호 변경 응답
else if (type == "change_password_response") {
handleChangePasswordResponse(json);
}
// 회원 정보 관련 응답-------------------------------------------------------------------------
// 게임 관련 응답-----------------------------------------------------------------------------
// 서버에서 보낸 방 목록 업데이트 응답 처리
else if (type == "room_list_update") {
roomArray = json["room_list"].toArray();
qDebug() << "서버로부터 방 목록 업데이트 받음. 방 개수:" << roomArray.size();
// 방 목록이 보이는 페이지라면 즉시 위젯을 업데이트합니다.
handleRoomListUpdate();
}
else if (type == "user_list_update") {
handleUserListUpdate(json);
}
// 방 생성 응답 처리 로직
else if (type == "create_room_response") {
handleCreateRoomResponse(json);
}
// 방 제거 응답 처리 로직
else if (type == "delete_room_response") {
handleDeleteRoomResponse(json);
}
// 방 입장 응답
else if (type == "join_room_response") {
handleJoinRoomResponse(json);
}
// 방 나가기 응답 처리 로직
else if (type == "leave_room_response") {
handleLeaveRoomResponse(json);
}
// 채팅 메시지 응답 처리 로직
else if (type == "chat_response") {
handleChatResponse(json);
}
// 게임 시작 응답 처리 로직
else if (type == "start_game_response") {
handleStartGameResponse(json);
}
// 게임 채팅 응답 처리 로직
else if (type == "game_chat") {
handleGameChat(json);
}
else if (type == "liar_answer_phase") {
handleLiarAnswerPhase(json);
}
else if (type == "vote_phase") {
handleVotePhaseResponse(json);
}
else if (type == "game_end"){
handleGameEndResponse(json);
}
// 서버 메시지 출력 응답 처리 로직
else if (type == "system_message") {
handleSystemMessage(json);
}
}
// 서버에게 요청 전송
void MainWindow::SendRequestToServer(const QJsonObject& request)
{
// QByteArray에 데이터를 직렬화합니다.
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_6_0);
// 4바이트 크기 헤더를 위한 공간을 확보합니다.
out << (quint32)0;
// JSON 객체를 바이트 배열로 직렬화합니다.
QJsonDocument doc(request);
out << doc.toJson(QJsonDocument::Compact);
// 헤더를 다시 쓰고, 전체 데이터 크기를 업데이트합니다.
out.device()->seek(0);
out << (quint32)(block.size() - sizeof(quint32));
// 최종 데이터를 소켓으로 전송합니다.
socket->write(block);
qDebug() << "JSON 데이터 전송 성공 (총 크기:" << block.size() << "바이트)";
}
06. 개인개발후기
라이어 게임을 만들면서 C++도 아직 익숙하지 않는데 그보다 익숙치 않은 Qt를 다루게 되어 코드를 작성하는 동시에 공부해야할 부분이 많았기 때문에 예상보다 많은 시간이 걸렸습니다.
단순히 이미지를 삽입하는 작업조차 경로를 직접 설정해야 하는 등 사소한 부분에서 시행착오가 많았습니다.
클라이언트–서버–DB 구조를 설계할 때는, 먼저 연결 과정을 확실히 구축한 덕분에 이후에는 연결 문제에 대한 부담을 줄일 수 있었습니다.
또한 클라이언트와 서버 간의 요청·응답을 처리하는 함수를 별도로 정의해 둔 덕분에, 새로운 로직을 구현할 때 기존 함수를 재사용하기만 하면 되어 매우 편리했습니다.
C++을 사용했음에도 불구하고 여전히 C 언어식 절차지향적인 코드가 많이 나온 점은 아쉬움으로 남습니다.
앞으로는 의식적으로라도 여러 기능을 담당하는 클래스를 정의하고, 그 관계를 설계하는 데 더 많은 노력을 기울여야겠다고 생각합니다.
'C++ > Project' 카테고리의 다른 글
| [LMS7 24/28주차] 1024 MFC 프로젝트, "CanSCan" 완료 보고서 (0) | 2026.05.06 |
|---|---|
| [LMS7 24/28주차] 1024 MFC 프로젝트, "CanSCan" 개발계획서 (0) | 2026.05.06 |
| [LMS7 16/26주차] 0826 [제60회 전국기능경기대회] 전시 작품 제작 프로젝트, "스마트홈" 완료 보고서 (0) | 2025.11.11 |
| [LMS7 16/26주차] 0826 [제60회 전국기능경기대회] 전시 작품 제작 프로젝트, "스마트홈" 개발 계획서 (0) | 2025.11.11 |
| [LMS7 14/28주차] 0812 Qt 개인 프로젝트, "라이어게임" 개발 계획서 (6) | 2025.08.17 |
