앞서 만들었던 오목에 자신감을 얻고 다음 프로젝트로 리버시를 만들기로 한다.
리버시 게임은, 돌을 놓아서 자기편의 돌 사이에 상대편의 돌이 끼어 있는 형태를 만들게 되면 따먹을 수 있는데, 단, 따먹은 돌은 없어지는 게 아니라 자기편의 돌로 변한 채로 그 자리에 있게 된다. 즉, 상대편의 돌을 뒤집어 자기편의 돌로 만드는 것이다.
완성된 게임은 아래에서...
https://sad-jennings-0ef849.netlify.app/
HTML,CSS는 별거 없고, 게임승리 이미지를 안보이게 넣어뒀다가 오목이 만들어졌을 때 나타나게 했다.
이번에는 P5.js를 사용했기 때문에, P5.js파일을 읽어와야 된다.
처음 시작할 때는 오목보다 쉽게 만들 수 있을 줄 알았다. 그런데, 두 배는 더 어렵게, 시간을 더 들여서 만들었다.
오목에서보다 더 고려해야 할 점이,
] 돌을 놓을 수 있는 곳이 제한되어 있다. 오목처럼 아무곳에나 둘 수 있는 것이 아니어서, 둘 수 있는 칸을 매번 계산해서 보여줘야 된다. (검정색,하얀색 조그만 사각형)
] 게임 막판 즈음에는 돌을 놓을 수 없는 경우가 발생하고, 그렇게되면 1차는 상대방에게 순서를 넘겨야되고, 그 다음에도 놓을 곳이 없으면 게임 종료 시킨다.
] 오목은 돌의 색깔이 정해지면 계속 그대로이지만, 리버시는 매번 색깔이 바뀐다.
] 마지막에 자기 돌의 수가 많은 쪽이 이기는 게임이라, 돌의 수를 매번 세어서 보여줘야 한다.
오목에서처럼 각 칸을 숫자의 배열로 구성시키고, 거기서 연산을 해나간다.
아래와 같이 빈 칸은 -1, 흰색 돌 0, 검정색 돌 1, 흰색이 둘 수 있는 곳 2, 검정색이 둘 수 있는 곳 3으로 숫자 배열을 계속 바꿔나가고, 그 배열에 맞춰 매 번 캔버스에 그림을 그려준다.
돌을 놓을 수 있는 곳은, 돌을 잡을 수 있는 곳에만 놓을 수 있는데, 이 경우를 찾는 곳에서 가장 애를 먹었던 것 같다.
매 번 각각의 칸에서 8방향을 모두 체크하고, 나와 다른 색깔의 돌이 있으면, 그 방향으로 다음 돌을 확인해나가다가, 그 끝에 나와 동일한 색의 돌이 있는 경우가 돌을 놓을 수 있는 경우다.
돌이 놓아지는 경우에도 그 돌의 8방향을 모두 확인해서 잡은 돌의 색을 바꿔준다. (배열의 숫자 0을 1로, 아니면 1을 0으로...)
아래에 Javascript 파일 전문. P5.js를 사용했지만, 그림 그리는 것만 좀 편해진 것이고, javascript의 기본 원리는 동일하다.
function setup(){
canvasWidth = 600;
canvasHeight = 600;
cvs= createCanvas(canvasWidth,canvasHeight);
background('#1b5e20');
row = 8;
rowWidth = canvasWidth / row;
radius = rowWidth-20;
count = 1; // 돌을 놓는 차례. 1을 검정색으로 설정
blackScore = whiteScore = 2;
blackAvailable = whiteAvailable = 2;
pass = 0;
checkDirection = [[1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1],];
board = new Array();
buffer = new Array();
boardHistory = new Array();
tik = new Audio("tik.mp3");
beep = new Audio("beep.wav");
ending = new Audio("ending.wav");
plz = new Audio("plz.m4a");
oneMore = new Audio("oneMore.m4a");
winScreen = document.querySelector('.winShow');
turnPass = document.querySelector(".turnPass");
// 보드 격자를 그리고, 모든 칸에 -1을 입력
boardInit();
// 시작 돌 4개
board[3][3] = board[4][4] = 1;
board[4][3] = board[3][4] = 0;
checkAvailable();
drawBoard();
document.querySelector("#black").innerHTML = "검정색 점수 : " + blackScore;
document.querySelector("#white").innerHTML = "하얀색 점수 : " + whiteScore;
button1 = document.querySelector("#withdraw");
button2 = document.querySelector("#reload");
troImg = document.querySelector("#trophyImg");
// 무르기 버튼 동작
button1.addEventListener('mouseup', ()=>{
plz.currentTime = 0.5;
plz.play();
withdraw();
})
button2.addEventListener('mouseup', ()=>{
oneMore.currentTime = 0.5;
oneMore.play();
setTimeout(() => {
location.reload();
}, 2000);
})
canvasPosition = cvs.position(0,30);
}
function boardInit(){
// 보드 격자를 그리고, 모든 칸에 -1을 입력
stroke("black");
strokeWeight(2);
fill('#1b5e20');
for (i = 0; i < 8; i++) {
board[i] = new Array(8);
for (j = 0; j < 8; j++) {
rect(i * rowWidth, j * rowWidth, rowWidth, rowWidth);
board[i][j] = -1;
}
}
console.log('-1 초기화')
fill("black");
circle(rowWidth * 2, rowWidth * 2, 10);
circle(rowWidth * 2, rowWidth * 6, 10);
circle(rowWidth * 6, rowWidth * 2, 10);
circle(rowWidth * 6, rowWidth * 6, 10);
}
// 마우스 클릭하면 그 칸의 위치 파악
document.addEventListener("mouseup", (e) => {
// console.log('offset' , e.offsetX, e.offsetY);
// console.log('page' , e.pageX, e.pageY);
if(e.offsetX > 0 && e.offsetX < canvasWidth && e.pageY > canvasPosition.y && e.offsetY > 0 && e.offsetY < canvasHeight){
let rowX = floor(e.offsetX / rowWidth);
let rowY = floor(e.offsetY / rowWidth);
main(rowX, rowY);
}
});
// 객체 복사하는데, 이전 값을 계속 참조하는 것을 막는 함수
function cloneObj(obj){
const result = [];
// console.log(obj)
for( i = 0 ; i< 8 ;i++ ){
result[i] = JSON.parse(JSON.stringify(obj[i]));
}
return result;
}
// 메인 함수. 클릭된 칸에 흰색(0), 검은색(1) 입력하고,
// 뒤집어야 하는 함수 호출, 보드 전체 그리는 함수 호출
function main(x, y) {
// 돌을 놓을 수 있는 곳일때만 주요 함수들 실행
if (board[y][x] == 2 || board[y][x] == 3) {
tik.currentTime = 0.5;
tik.play();
board[y][x] = count % 2;
// 돌을 놓을 수 있는 위치에 3(검정)이나 2(흰색)로 보드에 입력하는 함수 호출
checkReversible(x, y);
count++;
// 돌을 뒤집어야 하는 경우에, 바뀐 색으로 보드에 입력
checkAvailable();
let boardTemp = board;
// board배열상태로 보드를 그려줌
drawBoard();
// 검정,하얀 돌 개수 계산
scoreCalc();
// 무르기를 위해서 매 차례의 보드 배열을 통째로 boardHistory배열에 입력
boardHistory.push(cloneObj(board));
// console.log('boardHistory', boardHistory);
} else {
beep.play();
console.log('Not Here')
}
}
// 무르기 함수
function withdraw(){
boardInit();
boardHistory.pop();
board = cloneObj(boardHistory.slice(-1)[0]);
// board = boardHistory.slice(-1)[0];
console.log(boardHistory);
count--;
drawBoard();
scoreCalc();
}
// 점수 계산 함수
// 점수 계산 및 돌을 둘 수 있는 곳이 몇 개 있는지도 확인
// 만약 돌을 둘 수 있는 곳이 없으면, 나중에 패스하도록
function scoreCalc(){
blackScore = whiteScore = 0;
blackAvailable = whiteAvailable = 0;
for(i=0;i<8;i++){
blackScore += board[i].filter((e) => e === 1).length;
whiteScore += board[i].filter((e) => e === 0).length;
blackAvailable += board[i].filter((e) => e === 3).length;
whiteAvailable += board[i].filter((e) => e === 2).length;
}
document.querySelector("#black").innerHTML = "검정색 점수 : " + blackScore;
document.querySelector("#white").innerHTML = "하얀색 점수 : " + whiteScore;
if (count % 2 == 0 && whiteAvailable == 0 && pass != 2) {
console.log("no white move");
pass++;
}
if (count % 2 == 1 && blackAvailable == 0 && pass != 2) {
console.log("no black move");
pass++;
}
// 놓을 곳이 없으면 상대방에게 차례 넘김
if(pass == 1){
console.log("다음 차례로 넘어갑니다.");
turnPass.style.zIndex = 3;
turnPass.style.visibility = "visible";
turnPass.style.animationName = "moveDown";
console.log(turnPass);
setTimeout(() => {
turnPass.style.zIndex = -1;
}, 2500);
count++;
}
// 혹시 둘 곳이 없어서 다음 차례로 넘어간 경우 때문에
// 가능한 곳을 다시 찾고, 다시 그려주는 함수 호출
pass++;
checkAvailable();
drawBoard();
}
// 배열 상태대로 보드 전체에 돌 및 놓을 수 있는 위치를 그려줌
function drawBoard(){
noStroke();
// board배열에 따라 흰색,검정색,작은 사각형 그려주고
for (i = 0; i < 8; i++) {
for (j = 0; j < 8; j++) {
switch (board[i][j]) {
case 0:
fill("white");
circle((j+1)*rowWidth - floor(rowWidth/2), (i+1)*rowWidth - floor(rowWidth/2), radius);
break;
case 1:
fill("black");
circle((j+1)*rowWidth - floor(rowWidth/2), (i+1)*rowWidth - floor(rowWidth/2), radius);
break;
case 2:
fill("rgba(255,255,255,1)");
rect((j+1)*rowWidth - floor(rowWidth/2)-4, (i+1)*rowWidth - floor(rowWidth/2)-4, 10, 10 );
break;
case 3:
fill("rgba(0,0,0,1)");
rect((j+1)*rowWidth - floor(rowWidth/2)-4, (i+1)*rowWidth - floor(rowWidth/2)-4, 10, 10 );
break;
default :
fill("#1b5e20");
rect((j+1)*rowWidth - floor(rowWidth/2)-8, (i+1)*rowWidth - floor(rowWidth/2)-8, 16, 16 );
break;
}
}
}
}
// 원을 뒤집어야 하는지 여부 확인해서 0(하얀색),1(검정색)을 좌표에 입력
function checkReversible(x,y){
// 8방향으로 체크하는 루프
for (j = 0; j < checkDirection.length; j++) {
cX = x;
cY = y;
while (cX >= 0 && cY >= 0 && cX < 8 && cY < 8) {
cX = cX + checkDirection[j][0];
cY = cY + checkDirection[j][1];
if (cX < 0 || cY < 0 || cX >= 8 || cY >= 8) {
break;
} else {
if (board[cY][cX] == count%2 || board[cY][cX] == -1 || board[cY][cX] == 2 || board[cY][cX] == 3) {
buffer.push([cX, cY]);
break;
} else {
buffer.push([cX, cY]);
}
}
}
// console.log(buffer)
if (buffer.length >= 1) {
if (
count%2 == board[buffer[buffer.length - 1][1]][buffer[buffer.length - 1][0]]
) {
// console.log('here', count % 2);
for (i = 0; i < buffer.length; i++) {
board[buffer[i][1]][buffer[i][0]] = count%2;
}
}
}
buffer = [];
}
}
// 돌을 놓을 수 있는 위치 파악해서, 검정색 가능위치(3), 하얀색 가능위치(2)로 board에 기록
function checkAvailable(){
// 둘 곳 없어서 패스된게 두 번이 되면 게임 종료
if(pass>1){
console.log('game over')
turnPass.style.visibility="hidden";
gameOver();
}
// 이전 차례에서 2,3이었던 값을 -1로 초기화하는 함수 호출
resetAvailable();
boardCopy = board;
// console.log(boardCopy)
for (i=0;i<checkDirection.length;i++){
for(k=0;k<checkDirection.length;k++){
tX = i;
tY = k;
for (j = 0; j < checkDirection.length; j++) {
// 검사할 좌표들을 aX,aY로 설정
aX = tX;
aY = tY;
// 8방향으로 검사하는 루프
while (aX >= 0 && aY >= 0 && aX < 8 && aY < 8) {
aX += checkDirection[j][0];
aY += checkDirection[j][1];
// 검사 영역이 8x8 판 밖으로 나가면 멈추고,
if (aX < 0 || aY < 0 || aX >= 8 || aY >= 8) {
break;
}
// 판 안에서 검사하는 칸들을 배열에 넣는다.
else {
buffer.push([aX, aY, boardCopy[aY][aX]]);
// 검사한 칸의 색이 이번 차례의 색과 같거나, 빈 칸이면 배열에 넣기를 멈춘다.
// 아니라면 검사한 칸의 값을 계속 배열에 넣는 루프를 반복한다.
if (
boardCopy[aY][aX] === count % 2 ||
boardCopy[aY][aX] === -1 ||
boardCopy[aY][aX] === 2 ||
boardCopy[aY][aX] === 3 ||
boardCopy[tY][tX] !== -1
) {
break;
}
}
}
// 만약 돌들의 배열이 돌을 놓을 수 있는 조건이면 3(검은색)이나 2(흰색)을 입력한다
if (
buffer.length > 1 &&
boardCopy[buffer[0][1]][buffer[0][0]] != count % 2 &&
boardCopy[buffer[buffer.length - 1][1]][buffer[buffer.length - 1][0]] === count%2
) {
// console.log(count);
switch (count % 2 ) {
case 0:
board[tY][tX] = 2;
break;
case 1:
board[tY][tX] = 3;
break;
}
pass = 0;
}
buffer = [];
}
}
}
}
function resetAvailable(){
for (i=0;i<row;i++){
for(k=0;k<row;k++){
if(board[i][k]==2 || board[i][k]== 3){
board[i][k]= -1;
}
}
}
};
// 승리 화면 표시
function gameOver(x) {
ending.play();
// 음악이 재생되도록 약간의 시차를 두고 화면 표시
setTimeout(() => {
winScreen.style.visibility = 'visible';
winScreen.style.zIndex = 2;
if(blackScore > whiteScore){
document.querySelector(".comment").innerHTML = "<br>" + "검정색 승리";
}
if(blackScore < whiteScore){
document.querySelector(".comment").innerHTML = "<br>" + "하얀색 승리";
}
if(blackScore == whiteScore){
document.querySelector(".comment").innerHTML = "<br>" + "무승부";
}
console.log(winScreen)
troImg.style.animationName = "trophy";
}, 300);
}
|
cs |
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.
'문과생의 coding' 카테고리의 다른 글
Javascript 오목 게임 만들기 (3) | 2021.02.20 |
---|---|
Javascript 마우스 드래그한 방향으로 공 날리기 (0) | 2021.02.13 |
Javascript 캔버스에서 튕기는 공들 (w 중력) (0) | 2021.02.08 |
Javascript 캔버스에서 튕기는 공들 (w/o 중력) (0) | 2021.02.07 |
Javascript 캔버스에서 튕기는 공 w 중력 (0) | 2021.01.10 |