Files
sake/build/resources/main/static/mobile/js/bnft/rougame/Rougame.js

438 lines
19 KiB
JavaScript

/**
* Rougame.js
*
* 룰렛 게임 기능을 구현한 모듈
* 사용자가 룰렛을 돌려 다양한 보상을 획득할 수 있는 기능 제공
*
* 주요 기능:
* 1. 캔버스 기반 룰렛 UI 구현
* 2. 룰렛 회전 애니메이션 및 결과 처리
* 3. 서버와의 통신을 통한 결과 확인
* 4. 남은 게임 횟수 관리
*/
document.addEventListener('DOMContentLoaded', function() {
// 룰렛 세그먼트 데이터 가져오기 (각 칸의 정보)
const segmentElements = document.querySelectorAll('.segment-data');
const segments = Array.from(segmentElements).map(el => ({
rank: parseInt(el.dataset.rank), // 순위 정보
code: el.dataset.code, // 코드 정보
label: el.dataset.label // 표시될 텍스트
}));
// 화면 정보 및 설정값 가져오기
const screenInfo = {
scrRwdLinkUrl: document.getElementById('scrRwdLinkUrl').value, // 보상 링크 URL
bnfInnb: document.getElementById('bnfInnb').value // 혜택 고유 번호
};
/**
* RouletteGame 객체
* 룰렛 게임의 모든 기능을 구현한 객체
*/
const RouletteGame = {
canvas: document.getElementById('rouletteCanvas'), // 룰렛을 그릴 캔버스 요소
ctx: null, // 캔버스 컨텍스트
colors: ['#FDBE01', '#ffffff', '#FDBE01', '#ffffff', '#FDBE01', '#ffffff', '#FDBE01', '#ffffff'], // 룰렛 색상 배열
angle: 0, // 현재 룰렛 각도
segmentAngle: 2 * Math.PI / segments.length, // 각 세그먼트의 각도
diameterPercentage: 0.76, // 캔버스 대비 룰렛 지름 비율
canvasCenterX: 0, // 캔버스 중심 X 좌표
canvasCenterY: 0, // 캔버스 중심 Y 좌표
radius: 0, // 룰렛 반지름
isSpinning: false, // 회전 중 여부
rewardResult: null, // 서버에서 받은 보상 결과 저장
/**
* 초기화 함수
* 캔버스 설정 및 이벤트 리스너 등록
*/
init() {
this.ctx = this.canvas.getContext('2d');
this.canvasCenterX = this.canvas.width / 2;
this.canvasCenterY = this.canvas.height / 2;
this.radius = (Math.min(this.canvas.width, this.canvas.height) * this.diameterPercentage) / 2;
this.addEventListeners(); // 이벤트 리스너 등록
this.drawRoulette(); // 초기 룰렛 그리기
this.checkRewardAvailability(); // 보상 가능 여부 체크 추가
},
/**
* 이벤트 리스너 등록 함수
* 버튼 클릭 이벤트 처리
*/
addEventListeners() {
// 회전 버튼 이벤트 등록
const spinButton = document.getElementById('spinButton');
spinButton.addEventListener('click', this.spinRoulette.bind(this));
// 결과 팝업 닫기 버튼 이벤트 등록
const closeButton = document.querySelector('.microModal__btn');
closeButton.addEventListener('click', () => MicroModal.close('resultPopup'));
// 품절 모달의 메인 이동 버튼 이벤트 설정 (행운복권과 동일한 방식)
$('#btn-go-main').on('click', function() {
window.location.href = '/sopmal/bnft/bnftMain';
});
},
/**
* 룰렛 그리기 함수
* 캔버스에 룰렛과 세그먼트를 그림
*/
drawRoulette() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // 캔버스 초기화
let angle = this.angle;
// 각 세그먼트 그리기
segments.forEach((seg, index) => {
this.drawSegment(seg, index, angle);
angle += this.segmentAngle;
});
// 화살표 그리기
this.drawArrow();
},
/**
* 세그먼트 그리기 함수
* @param {Object} seg - 세그먼트 정보
* @param {number} index - 세그먼트 인덱스
* @param {number} angle - 시작 각도
*/
drawSegment(seg, index, angle) {
// 세그먼트 배경 그리기
this.ctx.beginPath();
this.ctx.fillStyle = this.colors[index % this.colors.length];
this.ctx.moveTo(this.canvasCenterX, this.canvasCenterY);
this.ctx.arc(this.canvasCenterX, this.canvasCenterY, this.radius, angle, angle + this.segmentAngle);
this.ctx.lineTo(this.canvasCenterX, this.canvasCenterY);
this.ctx.fill();
// 세그먼트 텍스트 그리기
this.ctx.save();
this.ctx.translate(this.canvasCenterX, this.canvasCenterY);
this.ctx.rotate(angle + this.segmentAngle / 2);
this.ctx.textAlign = 'right';
this.ctx.fillStyle = '#000000';
this.ctx.font = 'bold 20px "GmarketSans"';
this.ctx.fillText(seg.label, this.radius - 20, 10);
this.ctx.restore();
},
/**
* 화살표 그리기 함수
* 룰렛 결과를 가리키는 화살표 표시
*/
drawArrow() {
this.ctx.fillStyle = 'transparent';
this.ctx.beginPath();
this.ctx.moveTo(this.canvasCenterX, this.canvasCenterY - this.radius - 15);
this.ctx.lineTo(this.canvasCenterX - 10, this.canvasCenterY - this.radius + 5);
this.ctx.lineTo(this.canvasCenterX + 10, this.canvasCenterY - this.radius + 5);
this.ctx.closePath();
this.ctx.fill();
},
/**
* 화면 로딩시 보상 가능 여부 체크
*/
checkRewardAvailability() {
$.ajax({
url: '/sopmal/bnft/checkRewardAvailability',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
bnfInnb: screenInfo.bnfInnb
}),
success: (response) => {
console.log('checkRewardAvailability response:', response);
// 응답 결과 파싱
let result = response.resBody.result;
// 보상이 없는 경우 처리
if (result === "N" || result === false) {
// 게임 버튼 비활성화
this.disableGameButton();
// 품절 안내 모달 표시
this.showNoRewardModal();
return;
}
// 보상이 있는 경우 정상 진행
console.log('보상 가능 상태:', result);
},
error: (xhr, status, error) => {
console.error('보상 가능 여부 확인 실패:', error);
// 에러 발생 시에도 게임 버튼 비활성화
this.disableGameButton();
this.showErrorModal();
}
});
},
/**
* 게임 버튼 비활성화
*/
disableGameButton() {
const spinButton = document.getElementById('spinButton');
if (spinButton) {
spinButton.disabled = true;
spinButton.style.opacity = '0.5';
spinButton.style.cursor = 'not-allowed';
spinButton.textContent = '품절';
}
},
/**
* 보상 품절 안내 모달 표시
*/
showNoRewardModal() {
// 품절 전용 모달 표시
MicroModal.show('modal-no-reward');
},
/**
* 에러 안내 모달 표시
*/
showErrorModal() {
if (typeof showCustomAlert === 'function') {
showCustomAlert('보상 상태 확인 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.');
} else {
MicroModal.show('modal-alert');
document.querySelector('#modal-alert .microModal__content p').textContent =
'보상 상태 확인 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.';
}
},
/**
* 룰렛 회전 시작 함수 (수정된 버전)
*/
spinRoulette() {
// 이미 회전 중이면 중복 실행 방지
if (this.isSpinning) return;
// 버튼이 비활성화되어 있으면 실행 방지
const spinButton = document.getElementById('spinButton');
if (spinButton && spinButton.disabled) {
this.showNoRewardModal();
return;
}
// 남은 게임 횟수 확인
const remainingChancesElement = document.querySelector('.rouletteCnt b');
const remainingChances = parseInt(remainingChancesElement.textContent, 10);
if (isNaN(remainingChances) || remainingChances <= 0) {
// alert 대신 커스텀 모달 사용
if (typeof showCustomAlert === 'function') {
showCustomAlert('남은 횟수가 없습니다.');
} else {
// showCustomAlert 함수가 없는 경우 fallback
MicroModal.show('modal-alert');
document.querySelector('#modal-alert .microModal__content p').textContent = '남은 횟수가 없습니다.';
}
return;
}
this.isSpinning = true;
// 서버에 게임 결과 요청
$.ajax({
url: '/sopmal/bnft/playRougame',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
"bnfInnb": screenInfo.bnfInnb
}),
success: (response) => {
const result = JSON.parse(response.resBody.result);
// 에러 응답 처리
if (result.success === false && result.error) {
this.isSpinning = false;
console.log('Server error:', result.error);
// 모든 에러를 showCustomAlert로 처리
if (typeof showCustomAlert === 'function') {
showCustomAlert(result.error);
} else {
// showCustomAlert 함수가 없는 경우 fallback
MicroModal.show('modal-alert');
document.querySelector('#modal-alert .microModal__content p').textContent = result.error;
}
return;
}
// 정상 응답 처리
this.rewardResult = result;
const targetRank = result.rank;
const targetIndex = segments.findIndex(segment => segment.rank === targetRank);
console.log('Reward Result:', result);
console.log('Target Rank:', targetRank);
console.log('Target Index:', targetIndex);
console.log('Segments:', segments);
if (targetIndex !== -1) {
const adjustedIndex = targetIndex; // 12시 방향을 기준으로 인덱스 조정
this.startSpin(adjustedIndex); // 회전 애니메이션 시작
this.updateRemainingChances(); // 남은 횟수 업데이트
}
},
error: (xhr, status, error) => {
console.error("AJAX request failed:", error);
this.isSpinning = false;
// alert 대신 커스텀 모달 사용
if (typeof showCustomAlert === 'function') {
showCustomAlert('룰렛 회전 중 오류가 발생했습니다. 다시 시도해주세요.');
} else {
// showCustomAlert 함수가 없는 경우 fallback
MicroModal.show('modal-alert');
document.querySelector('#modal-alert .microModal__content p').textContent = '룰렛 회전 중 오류가 발생했습니다. 다시 시도해주세요.';
}
}
});
},
/**
* 회전 애니메이션 시작 함수
* @param {number} targetIndex - 목표 세그먼트 인덱스
*/
startSpin(targetIndex) {
const segmentsCount = segments.length;
const segmentAngleDeg = 360 / segmentsCount;
// 수정된 목표 각도 계산: 12시 방향이 0도가 되도록 조정
const targetAngleDeg = -(targetIndex * segmentAngleDeg) + 255; // 270도는 12시 방향
const initialAngle = this.angle % (2 * Math.PI);
const finalAngle = (targetAngleDeg * Math.PI / 180) + (2 * Math.PI * 5); // 5바퀴 추가 회전
console.log('Segments Count:', segmentsCount);
console.log('Segment Angle (deg):', segmentAngleDeg);
console.log('Target Angle (deg):', targetAngleDeg);
console.log('Initial Angle (rad):', initialAngle);
console.log('Final Angle (rad):', finalAngle);
// 회전 시간 및 속도 설정
const spinTimeTotal = Math.random() * 1000 + 4000; // 4~5초 사이 랜덤 시간
let spinTime = 0;
// 회전 애니메이션 함수
const rotateWheel = () => {
spinTime += 30; // 30ms마다 업데이트
if (spinTime >= spinTimeTotal) {
// 회전 완료
this.angle = targetAngleDeg * Math.PI / 180;
console.log('Final Position Angle (rad):', this.angle);
this.drawRoulette();
this.stopRotateWheel();
return;
}
// easeOut 함수로 감속 효과 적용
const spinAngle = this.easeOut(spinTime, 0, finalAngle - initialAngle, spinTimeTotal);
this.angle = initialAngle + spinAngle;
this.drawRoulette();
requestAnimationFrame(rotateWheel); // 다음 프레임 요청
};
rotateWheel(); // 애니메이션 시작
},
/**
* 회전 정지 및 결과 처리 함수
* 최종 위치에서 결과를 계산하고 팝업 표시
*/
stopRotateWheel() {
let degrees = (this.angle * 180 / Math.PI) % 360;
console.log('Current Angle in Degrees:', degrees);
if (degrees < 0) degrees += 360;
console.log('Normalized Angle in Degrees:', degrees);
const arcd = 360 / segments.length;
console.log('Arc Degree per Segment:', arcd);
// 수정된 인덱스 계산: 12시 방향을 기준으로 계산
const index = (Math.floor(((360 - degrees + 255) % 360) / arcd)) % segments.length;
console.log('Calculated Segment Index:', index);
// 보상 유형에 따라 결과 텍스트와 안내 문구 설정
let resultText = '';
let guideText = '';
if (this.rewardResult) {
if (this.rewardResult.rewardType === 'MILEAGE') {
// 마일리지인 경우
resultText = `마일리지 ${this.rewardResult.rewardAmount}원 획득하셨습니다.`;
guideText = 'MY혜택 > 마일리지에서 확인하세요.';
} else if (this.rewardResult.rewardType === 'COUPON') {
// 쿠폰인 경우
resultText = this.rewardResult.itemName;
guideText = 'MY혜택 > 쿠폰에서 확인하세요.';
} else {
// 기타 경우
resultText = segments[index] ? segments[index].label : '보상';
guideText = 'MY혜택에서 확인하세요.';
}
} else {
// rewardResult가 없는 경우 기본값
resultText = segments[index] ? segments[index].label : '보상';
guideText = 'MY혜택에서 확인하세요.';
}
console.log('Final Result Text:', resultText);
console.log('Guide Text:', guideText);
// 결과 텍스트와 안내 문구 업데이트
document.getElementById('resultText').textContent = resultText;
// 안내 문구도 동적으로 변경
const guideElement = document.querySelector('#resultPopup .microModal__content p:last-child');
if (guideElement) {
guideElement.textContent = guideText;
}
// 결과 팝업 표시
MicroModal.show('resultPopup');
this.isSpinning = false; // 회전 상태 초기화
this.rewardResult = null; // 결과 초기화
},
/**
* easeOut 애니메이션 함수
* 점점 느려지는 효과를 위한 계산
* @param {number} t - 현재 시간
* @param {number} b - 시작 값
* @param {number} c - 변화량
* @param {number} d - 지속 시간
* @returns {number} - 계산된 값
*/
easeOut(t, b, c, d) {
const ts = (t /= d) * t;
const tc = ts * t;
const result = b + c * (tc + -3 * ts + 3 * t);
console.log('EaseOut Calculation:', { t, b, c, d, result });
return result;
},
/**
* 남은 게임 횟수 업데이트 함수
* 게임 후 남은 횟수를 감소시킴
*/
updateRemainingChances() {
const remainingChancesElement = document.querySelector('.rouletteCnt b');
if (remainingChancesElement) {
let remainingChances = parseInt(remainingChancesElement.textContent, 10);
console.log('Current Remaining Chances:', remainingChances);
if (!isNaN(remainingChances) && remainingChances > 0) {
remainingChancesElement.textContent = remainingChances - 1;
console.log('Updated Remaining Chances:', remainingChances - 1);
}
}
}
};
// 룰렛 게임 초기화
RouletteGame.init();
});