438 lines
19 KiB
JavaScript
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();
|
|
});
|