아래와 같은 모양의 오셀로 게임을 Javascript로 만들어 보자.

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>오셀로 게임</title>

<style>
    body{
        margin:0;
        background:#222;
        color:white;
        font-family:Arial, sans-serif;
        display:flex;
        flex-direction:column;
        align-items:center;
    }

    h1{
        margin:20px 0 10px;
    }

    #info{
        margin-bottom:10px;
        font-size:20px;
    }

    #score{
        margin-bottom:15px;
        font-size:18px;
    }

    canvas{
        background:#0f7a28;
        border:6px solid #1a1a1a;
        box-shadow:0 0 20px rgba(0,0,0,0.5);
        cursor:pointer;
    }

    .buttons{
        margin-top:15px;
    }

    button{
        padding:10px 20px;
        font-size:16px;
        border:none;
        border-radius:8px;
        cursor:pointer;
    }

    button:hover{
        opacity:0.9;
    }
</style>
</head>
<body>

<h1>오셀로 (Reversi)</h1>

<div id="info">흑돌 차례</div>
<div id="score">흑 2 : 백 2</div>

<canvas id="board" width="640" height="640"></canvas>

<div class="buttons">
    <button onclick="resetGame()">다시 시작</button>
</div>

<script>
const canvas = document.getElementById("board");
const ctx = canvas.getContext("2d");

const info = document.getElementById("info");
const scoreText = document.getElementById("score");

const SIZE = 8;
const CELL = 80;

let board = [];
let currentPlayer = 1; // 1 흑, 2 백
let gameOver = false;

const directions = [
    [-1,-1], [0,-1], [1,-1],
    [-1,0],           [1,0],
    [-1,1],  [0,1],  [1,1]
];

function initBoard(){
    board = [];

    for(let y=0; y<SIZE; y++){
        board[y] = [];
        for(let x=0; x<SIZE; x++){
            board[y][x] = 0;
        }
    }

    // 시작 돌
    board[3][3] = 2;
    board[4][4] = 2;

    board[3][4] = 1;
    board[4][3] = 1;
}

function drawBoard(){
    ctx.clearRect(0,0,canvas.width,canvas.height);

    // 보드
    ctx.fillStyle = "#0f7a28";
    ctx.fillRect(0,0,canvas.width,canvas.height);

    // 격자
    ctx.strokeStyle = "#000";

    for(let i=0; i<=SIZE; i++){

        ctx.beginPath();
        ctx.moveTo(i*CELL,0);
        ctx.lineTo(i*CELL,canvas.height);
        ctx.stroke();

        ctx.beginPath();
        ctx.moveTo(0,i*CELL);
        ctx.lineTo(canvas.width,i*CELL);
        ctx.stroke();
    }

    // 돌
    for(let y=0; y<SIZE; y++){
        for(let x=0; x<SIZE; x++){

            if(board[y][x] !== 0){
                drawStone(x,y,board[y][x]);
            }
        }
    }

    // 놓을 수 있는 위치 표시
    if(!gameOver){
        drawHints();
    }

    updateScore();
}

function drawStone(x,y,player){

    const cx = x * CELL + CELL/2;
    const cy = y * CELL + CELL/2;

    const gradient = ctx.createRadialGradient(
        cx-8, cy-8, 5,
        cx, cy, 30
    );

    if(player === 1){
        gradient.addColorStop(0, "#777");
        gradient.addColorStop(1, "#000");
    }else{
        gradient.addColorStop(0, "#fff");
        gradient.addColorStop(1, "#ccc");
    }

    ctx.beginPath();
    ctx.arc(cx, cy, 30, 0, Math.PI*2);
    ctx.fillStyle = gradient;
    ctx.fill();

    ctx.strokeStyle = "#111";
    ctx.stroke();
}

function drawHints(){

    ctx.fillStyle = "rgba(255,255,0,0.35)";

    for(let y=0; y<SIZE; y++){
        for(let x=0; x<SIZE; x++){

            if(isValidMove(x,y,currentPlayer)){
                ctx.beginPath();
                ctx.arc(
                    x*CELL + CELL/2,
                    y*CELL + CELL/2,
                    10,
                    0,
                    Math.PI*2
                );
                ctx.fill();
            }
        }
    }
}

function inside(x,y){
    return x>=0 && y>=0 && x<SIZE && y<SIZE;
}

function opponent(player){
    return player === 1 ? 2 : 1;
}

function isValidMove(x,y,player){

    if(board[y][x] !== 0) return false;

    const enemy = opponent(player);

    for(const [dx,dy] of directions){

        let nx = x + dx;
        let ny = y + dy;

        let foundEnemy = false;

        while(inside(nx,ny) && board[ny][nx] === enemy){
            foundEnemy = true;
            nx += dx;
            ny += dy;
        }

        if(
            foundEnemy &&
            inside(nx,ny) &&
            board[ny][nx] === player
        ){
            return true;
        }
    }

    return false;
}

function flipStones(x,y,player){

    const enemy = opponent(player);

    for(const [dx,dy] of directions){

        let nx = x + dx;
        let ny = y + dy;

        let stones = [];

        while(inside(nx,ny) && board[ny][nx] === enemy){

            stones.push([nx,ny]);

            nx += dx;
            ny += dy;
        }

        if(
            stones.length > 0 &&
            inside(nx,ny) &&
            board[ny][nx] === player
        ){

            for(const [sx,sy] of stones){
                board[sy][sx] = player;
            }
        }
    }
}

function hasValidMove(player){

    for(let y=0; y<SIZE; y++){
        for(let x=0; x<SIZE; x++){

            if(isValidMove(x,y,player)){
                return true;
            }
        }
    }

    return false;
}

function updateScore(){

    let black = 0;
    let white = 0;

    for(let y=0; y<SIZE; y++){
        for(let x=0; x<SIZE; x++){

            if(board[y][x] === 1) black++;
            if(board[y][x] === 2) white++;
        }
    }

    scoreText.textContent =
        `흑 ${black} : 백 ${white}`;
}

canvas.addEventListener("click",(e)=>{

    if(gameOver) return;

    const rect = canvas.getBoundingClientRect();

    const x = Math.floor((e.clientX - rect.left) / CELL);
    const y = Math.floor((e.clientY - rect.top) / CELL);

    if(!inside(x,y)) return;

    if(!isValidMove(x,y,currentPlayer)) return;

    board[y][x] = currentPlayer;

    flipStones(x,y,currentPlayer);

    currentPlayer = opponent(currentPlayer);

    // 패스 처리
    if(!hasValidMove(currentPlayer)){

        currentPlayer = opponent(currentPlayer);

        if(!hasValidMove(currentPlayer)){

            gameOver = true;

            finishGame();
            drawBoard();
            return;
        }

        info.textContent =
            (currentPlayer === 1 ? "백" : "흑")
            + " 플레이어 패스!";
    }

    info.textContent =
        currentPlayer === 1
        ? "흑돌 차례"
        : "백돌 차례";

    drawBoard();
});

function finishGame(){

    let black = 0;
    let white = 0;

    for(let y=0; y<SIZE; y++){
        for(let x=0; x<SIZE; x++){

            if(board[y][x] === 1) black++;
            if(board[y][x] === 2) white++;
        }
    }

    if(black > white){
        info.textContent = `게임 종료 - 흑 승리 (${black}:${white})`;
    }
    else if(white > black){
        info.textContent = `게임 종료 - 백 승리 (${white}:${black})`;
    }
    else{
        info.textContent = `무승부 (${black}:${white})`;
    }
}

function resetGame(){

    currentPlayer = 1;
    gameOver = false;

    initBoard();

    info.textContent = "흑돌 차례";

    drawBoard();
}

initBoard();
drawBoard();
</script>

</body>
</html>

+ Recent posts