앞서 만들었던 오목에 자신감을 얻고 다음 프로젝트로 리버시를 만들기로 한다. 

 

Javascript 오목 게임 만들기

오목 게임. 아이들이랑 놀 겸해서 만들어봤다. 완성본은 아래에서... omog-geim.bryanko555.repl.co HTML,CSS는 별거 없고, 게임승리 이미지를 안보이게 넣어뒀다가 오목이 만들어졌을 때 나타나게 했다. 큰

mrkool.tistory.com

리버시 게임은, 돌을 놓아서 자기편의 돌 사이에 상대편의 돌이 끼어 있는 형태를 만들게 되면 따먹을 수 있는데, 단, 따먹은 돌은 없어지는 게 아니라 자기편의 돌로 변한 채로 그 자리에 있게 된다. 즉, 상대편의 돌을 뒤집어 자기편의 돌로 만드는 것이다.

완성된 게임은 아래에서...

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의 기본 원리는 동일하다.

 

 

모던 자바스크립트 Deep Dive:자바스크립트의 기본 개념과 동작 원리

COUPANG

www.coupang.com

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], [10], [11], [01], [-11], [-10], [-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 * 210);
   circle(rowWidth * 2, rowWidth * 610);
   circle(rowWidth * 6, rowWidth * 210);
   circle(rowWidth * 6, rowWidth * 610);
}
 
// 마우스 클릭하면 그 칸의 위치 파악
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)-410,  10  );
          break;
        case 3:
          fill("rgba(0,0,0,1)");
          rect((j+1)*rowWidth - floor(rowWidth/2)-4, (i+1)*rowWidth - floor(rowWidth/2)-410,  10  );
          break;
        default : 
          fill("#1b5e20");
          rect((j+1)*rowWidth - floor(rowWidth/2)-8, (i+1)*rowWidth - floor(rowWidth/2)-816,  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

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.

오목 게임. 아이들이랑 놀 겸해서 만들어봤다.

완성본은 아래에서...

omog-geim.bryanko555.repl.co

 

 

(오목 이후 제작한 게임)

 

Javascript 리버시(Reversi) 게임 만들기

앞서 만들었던 오목에 자신감을 얻고 다음 프로젝트로 리버시를 만들기로 한다. Javascript 오목 게임 만들기 오목 게임. 아이들이랑 놀 겸해서 만들어봤다. 완성본은 아래에서... omog-geim.bryanko555.re

mrkool.tistory.com

 

 

HTML,CSS는 별거 없고, 게임승리 이미지를 안보이게 넣어뒀다가 오목이 만들어졌을 때 나타나게 했다.

 

큰 틀에서, 바둑판의 돌이 놓여지는 눈금 하나하나가 배열로 되어있고, 돌이 없는 곳은 -1, 검은 돌이 놓여지면 1, 하얀 돌이 놓여지면 2로 배열이 구성되는 식이다.

원래 아래처럼 보이는 배열인데, 콘솔창에 위 그림처럼 보이도록 해야 x,y좌표를 찾기 쉬워진다.

아래와 같이 콘솔창에 눈금열만큼 줄바꿈을 넣어서 실제 바둑판처럼 숫자가 나열되어 보여주게 해준다.

오목에서 고려해야 할 요소들이다.

] 돌의 위치 x,y를 배열의 위치로 바꿔주고, 반대로 배열의 위치를 x,y좌표로 바꿔주는 함수가 필요하다.

  19칸 실제 바둑판에서, x=3,y=1는 배열의 22번째 위치이고, 배열의 5번째 위치는 x=4,y=0이다. (첫 줄은 0)

] 마우스 클릭한 위치를 파악해서 돌을 그리는데, 대충 찍어도 정확한 눈금에 돌이 그려지도록 보정한다.

] 그 자리에 이미 돌이 놓여져 있으면(그 위치의 숫자가 -1이 아니면) 그림이 안그려지게 하고,

  비어져 있으면 차례에 따라서 흑이나 백 돌을 그린다.

] 무르기 기능.  돌을 놓을 때마다 그 차례의 바둑판 배열 전체를 또 다른 배열(여기서는 lastBoard[ ])에 넣어나가다가, 무르기가 실행되면 바로 직전의 바둑판 배열을 불러와서 그대로 바둑판을 그려준다.

] 이제 가장 중요한 승패 판정. 돌이 놓여질때 그 돌의 가로,세로,대각선 여덟 방향으로 돌의 색깔들을 파악해서 다섯 개가 놓여졌는지를 보면 되는데, 그냥하면 마지막 돌이 양쪽 끝에 놓여졌을 때만 인지하기 때문에, 

돌이 오목의 중간에 놓여지면서 오목이 완성되는 경우도 파악할 수 있도록 고려해야 한다.

재미로 '무르기'와 '다시하기'에 우리 아들 목소리를 녹음해서 활용했다. 

음악 재생은 읽어서 변수에 할당하고, 재생함수 실행.

let audio1 = new Audio('tik.mp3');

let audio2 = new Audio('beep.wav');

audio1.play();

audio2.play();

 

전체 코드는 아래 링크에서... 

repl.it/@BryanKo555/omog-geim

 

승패확인 함수에서 돌이 오목의 중간에 놓여졌을 때도 5개인지를 확인할 수 있도록 하는 것에서 나름 고민을 했고, 이런 고민의 과정과 그 해결 됬을 때의 기쁨이 코딩하는 즐거움이라고 생각한다.

 

 

생활코딩! HTML + CSS + 자바스크립트 : 처음 프로그래밍을 시작하는 입문자의 눈높이에 맞춘

COUPANG

www.coupang.com

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.

 

LOL의 미스포츈의 궁처럼 공을 날려 보내봐야겠다.

정해진 각도를 중심으로 퍼져서 날라가는 총알

결과물은 아래와 같다.

deuraegeuhan-banghyangeuro-gong-ssogi-p5js.bryanko555.repl.co

이번에는 p5.js를 써봤다. 

내가 경험한 바로 말하면, p5.js는 그래픽에 도움되는 여러 Javascript명령을 손쉽게 구현할 수 있는 명령으로 대신할 수 있게 해준다. 

예를 들어, 아래와 같은 식이다.

vanila Javascript 원 그리기 p5.js 원 그리기
ctx.beginPath();
ctx.arc(x좌표,y좌표,반지름,0,Math.PI*2)
ctx.stroke();
circle(x좌표,y좌표,반지름);

p5.js를 사용하더라도 Javascript의 기본 개념은 변함 없다. 

p5.js를 사용하려면, html  첫 머리에 코드를 삽입해준다.

<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.min.js"></script>

 

위와 같이 공을 날리기 위해서 필요한 요소들이다. 

  - 드래그 시작점

  - 드래그 종료점

  - 시작점과 종료점의 거리 (이 거리의 길이를 공의 속도로 사용했다)

  - 시작점과 종료점의 각도 (이 각도를 중심으로 공이 랜덤하게 날라간다)

  - 공이 이동할 때의 x,y 변경 값

드래그 시작점과 종료점은 마우스가 눌려졌을 때(시작)와 마우스가 놓여졌을때(종료)의 x,y좌표를 기록한다. 

 

// 마우스를 클릭했을 때 x,y 저장

function mousePressed(){

      x2 = mouseX;

      y2 = mouseY;

    };

 

// 마우스를 뗐을때 x,y 저장하고, 간격을 두고 공을 생성하는 함수 init 호출

function mouseReleased(){

    x = mouseX;

    y = mouseY;

    if(abs(x - x2) <= 20 && abs(y - y2) <= 20){return} // 드래그를 너무 조금했을 때에는 무시

    num = 0;

    repeat = setInterval(function(){  // 공들이 한번에 다 날아가지 않도록 약간씩 시차를 두고 생성시켜준다.

        init(x,y);},100);

    draw();  // 애니메이션을 구현할 함수를 호출

    };

위의 마우스 관련 함수들도 p5.js로 간단히 구현했다.

 

그리고, 드래그 하는 동안에 직선을 그려줘야하니까, 

// 드래그할 때 붉은 선 그리기

function mouseDragged(){ // 마우스 버튼이 눌린 상태에서 움직일 때마다 한 번씩 호출

    stroke("red");

    strokeWeight(5);

    line(x2, y2, mouseX, mouseY);

};

 

공들을 생성하는 함수. 

공들이 날아가는 각종 속성을 정하기 때문에 가장 중요하겠다. 

function init(x,y){ // 공을 하나씩 생성하고 속성 부여

    p = sqrt(pow((x2-x),2) + pow((y2-y),2));

    radian = atan2((x2-x),(y2-y));

    dx = (p *sin(radian + ((random(20)-10)*PI/180))*0.05) ;

    dy = (p * cos(radian + ((random(20)-10)*PI/180))*0.05) ;

    c = 'rgb('+floor(random(255))+','+floor(random(255))+','+floor(random(255))+')';

    balls[num] = new Ball(x, y, x2, y2, dx, dy, p, c)

    if(num<ballNumber){num++} else {

        clearInterval(repeat);  // 공이 지정 숫자만큼 생성됬으면, init() 함수 호출을 중지.

        }    ;

}

 

위에서 dx,dy가 공들을 이동하는 x,y의 변화값인데, 

아래 그림의 c에다가 각도에서 랜덤하게 차이를 두고, 거기서 cos,sin을적용해서 dx,dy를 구했다.

a = c * cos(각도)

b = c * sin(각도)

물론 a = x - x2 , b = y - y2 로도 구할 수 있지만, 랜덤한 각도로 발사가 되어야 해서, 빙~ 돌아서 값을 구했다. (뭔가 더 좋은 방법이 있을 것 같기는 하다. ㅡㅡa)

 

마우스를 떼면 draw()함수를 실행시키는데, p5.js에서는 draw() 함수는 requestAnimationFrame이 없어도 기본적으로 프레임마다 호출되어 실행된다. 

 

짧게 드래그 했을 때 길게 드래그 했을 때

전체코드 (HTML)

    <div id="inputArea">
    <label for="ballNumberInput">공 갯수 : </label>
    <input type="text" id='inputBallNumber' name='ballNumberInput' size='3' value="10" onkeyup="setup()">
    </div>
    <div id="msg">마우스를 왼쪽클릭 후 드래그 했다가 놓으세요<br> 드래그 한 길이가 날라가는 힘이 됩니다.</div>
</body>
cs

전체코드 ( Javascript)

function setup() {
    createCanvas(windowWidth,windowHeight);
    background('white');
    num = 0;
    
    ballNumber = document.querySelector('#inputBallNumber').value; 
    balls = [];
    radius = 30;
}
 
class Ball {
    constructor(x,y,x2,y2,dx,dy,power,color){
        this.x = x; // 공이 발사되는 x
        this.y = y; // 공이 발사되는 y
        this.x2 = x2; // 드래그 시작점 x
        this.y2 = y2; // 드래그 시작점 y
        this.dx = dx; // 공이 이동하는 x 크기
        this.dy = dy; // 공이 이동하는 y 크기
        this.p = power; //마우스를 드래그한 길이, 공이 날라가는 힘(속도),
        this.c = color; // 공 색깔
    }
    
    update(){ // 공을 변화값만큼 이동
        this.x = this.x + this.dx;
        this.y = this.y + this.dy;
    }
    
    drawBall(){ // 프레임마다 변경된 위치로 공을 그려줌
        fill(this.c);
        stroke('black'); 
        strokeWeight(2);
        circle(this.x , this.y, radius);
    }
}
 
function draw(){ 
    fill("rgba(255,255,255,0.8)");
    rect(00, canvas.width, canvas.height);
    
    stroke('black');
    strokeWeight(2);
    for (i =0; i < num; i++) {
        balls[i].update();
        fill(balls[i].c);
        circle(balls[i].x, balls[i].y, radius);
        
        if (balls[num-1].x+radius*2 < 0 || balls[num-1].x - radius*2 > canvas.width || balls[num-1].y + radius*2 < 0 || balls[num-1- radius*2 > canvas.height) {
            return// 마지막 공이 완전히 캔버스 밖으로 나가면 공 그리기 종료, 
        } 
    } 
}
 
function init(x,y){ // 공을 하나씩 생성하고 속성 부여
    p = sqrt(pow((x2-x),2+ pow((y2-y),2));
    radian = atan2((x2-x),(y2-y));
    dx = (p *sin(radian + ((random(20)-10)*PI/180))*0.05) ;
    dy = (p * cos(radian + ((random(20)-10)*PI/180))*0.05) ;
    c = 'rgb('+floor(random(255))+','+floor(random(255))+','+floor(random(255))+')';
    balls[num] = new Ball(x, y, x2, y2, dx, dy, p, c)
    if(num<ballNumber){num++else {
        clearInterval(repeat);
        }    ;
}
 
// 마우스를 클릭했을 때 x,y 저장
function mousePressed(){
      x2 = mouseX;
      y2 = mouseY;
      print(mouseIsPressed)
    };
 
// 드래그할 때 붉은 선 그리기
function mouseDragged(){ // 마우스 버튼이 눌린 상태에서 움직일 때마다 한 번씩 호출
    stroke("red");
    strokeWeight(5);
    line(x2, y2, mouseX, mouseY);
};
 
// 마우스를 뗐을때 x,y 저장하고, 간격을 두고 공을 생성하는 함수 init 호출
function mouseReleased(){
    x = mouseX;
    y = mouseY;
    if(abs(x - x2) <= 20 && abs(y - y2) <= 20){return// 드래그를 너무 조금했을 때에는 무시
    num = 0;
    repeat = setInterval(function(){
        init(x,y);},100);
    draw();
    };
 
 
cs
 

자바스크립트를 깨우치다:객체로 풀어보는 JavaScript의 원리

COUPANG

www.coupang.com

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.

 

지난 글에서 여러 공들이 화면 안에서 튕기게 만들었는데, 이제 여기다가 중력을 가미해보자.

 

 

Javascript 캔버스에서 튕기는 공들 (w/o 중력)

이전 글에서는 공 하나가 화면 안에서 생각없이 튕기는 내용이었다. Javascript 캔버스에서 움직이는 공 그리기 유튜브에서 브라우저에서 각종 도형의 움직임을 Javascript로 구현하는 걸 보고 반해

mrkool.tistory.com

이 공들에 중력이 더해지면 아래와 같이 실제와 비슷한 운동을 보인다.

https://repl.it/join/vtffyilt-bryanko555 

대부분 동일하고, 프레임마다 다르게 그려주는 update()함수에 가속도 요소를 넣어준다. 

 

update(){ // 프레임마다 속성들을 변화시킴

  this.y += this.weight; // y방향 움직임을 무게로 가정해서 설정

  this.x += this.directionX;

  this.weight += 0.1; // 프레임마다 떨어질때의 가속도 증가

  this.directionX *= 0.997; // 가로방향 속도 감소. 가장 자연스러운 움직임을 보이는 수치로 찾은 값 

  if(this.y+this.size>canvas.height || this.y-this.size<0){ // 세로 방향 바운드 처리

    this.weight *= -0.8; // y 방향을 바꿔주면서 점차 바운스가 감소하게

  if(Math.abs(this.weight)<0.9){this.y = canvas.height-this.size; // 바운드가 어느 정도 이하가 되면 더 이상 바운드 안하게, 즉, 바닥에 붙게 한다.

  this.weight =1;} else { this.y -=1 ;} //

  };

  if(this.x>canvas.width-this.size || this.x-this.size < 0 ) { // 가로 방향 바운드 처리

    this.directionX *= -1; // x 방향 전환. 

  }

}

 

전체 코드 :

 

window.onload = function () {
 
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = window.innerWidth-15;
  canvas.height = window.innerHeight-15;
 
  balls=[];
  ballNumber = 50;
 
  class Ball{
    constructor(x,y){ // ball의 기본 속성들을 정의 
      this.x = x;
      this.y = y;
      this.c = 'rgba('+Math.random()*255+','+Math.random()*255+','+Math.random()*255+')';
      this.size = Math.random()*10+5;
      this.angle = (Math.random()*360)*((Math.PI*2)/360);
      this.power = Math.random()*3+2;
      this.directionX = this.power * Math.cos(this.angle);
      this.weight = this.power * Math.sin(this.angle);
    }
    update(){ // 프레임마다 속성들을 변화시킴 
      this.y += this.weight; 
      this.x += this.directionX;
      this.weight += 0.1// 프레임마다 떨어질때의 가속도 증가
      this.directionX *= 0.997// 가로방향 속도 감소. 
      if(this.y+this.size>canvas.height || this.y-this.size<0){ // 세로 방향 바운드 처리
        this.weight *= -0.8// y 방향을 바꿔주면서 점차 바운스가 감소하게
        if(Math.abs(this.weight)<0.9){this.y = canvas.height-this.size; // 바운드가 어느 정도 이하가 되면 더 이상 바운드 안하게, 즉, 바닥에 붙게 한다.
        this.weight =1;} else { this.y -=1 ;} // 
        };
      if(this.x>canvas.width-this.size || this.x-this.size < 0 ) { // 가로 방향 바운드 처리 
        this.directionX *= -1// x 방향 전환.  
      } 
    }
    draw(){ // 넘어온 속성값대로 캔버스에 그림을 그려줌
       ctx.fillStyle= this.c;
       ctx.beginPath();
       ctx.arc(this.x, this.y, this.size, 0, Math.PI*2true);
       ctx.closePath();
       ctx.fill();
       ctx.strokeStyle = 'black';
       ctx.strokeWidth = 4;
       ctx.stroke();
    }
  }
 
  function init(){
      for(i=0;i<ballNumber;i++){
        balls[i] = new Ball(canvas.width*0.5, canvas.height*0.5)
      }
  }
 
  function animate(){ // 매 프레임마다 벌어지는 일들
    ctx.fillStyle='rgba(255,255,255,0.51)';
    ctx.fillRect(0,0,canvas.width,canvas.height);
    for(i=0;i<ballNumber;i++){
      balls[i].update();
      balls[i].draw();
    }
 
    window.addEventListener('resize',function(){ // 화면 크기가 변하면 캔버스 크기도 변경해줌
      canvas.width=window.innerWidth;
      canvas.height=window.innerHeight;
    })
    requestAnimationFrame(animate);
  }
 
  init();
  animate();
 
  }
cs

여기다가 좀 변화를 줘서, 공들을 360도 회전하면서 발사되게 해봤다.

https://repl.it/join/zxxofqqj-bryanko555

공이 한꺼번에 생성되는 것이 아니라, 하나씩 생성되서 발사되야 하기 때문에 setInterval로 시간 간격을 두면서 공을 생성해야 한다. requestAnimationFrame으로는 공들이 순식간에 생성되서 그냥 동시에 터지는 것처럼 보인다.

setInterval이 호출될 때마다 공을 한개씩 생성한다.그리고 생성된 공이 시계방향으로 돌아가면서 발사되야하기 때문에, 공(ball)의 속성에 각도(angle)를 추가했다.

 

class Ball{

  constructor(x,y,angle){ // ball의 기본 속성들을 정의

    :

}

 

function init(){

  if(num < ballNumber){ num++;} // 호출될 때마다 공을 1개씩 생성한다.

  balls[num] = new Ball(canvas.width*0.5, canvas.height*0.5, Math.PI*2/360 + i) // 각도를 순차적으로 증가시키면서 공을 생성한다.

}

 

setInterval(init,100);

 

오늘은 여기까지....

 

 

자바스크립트를 깨우치다:객체로 풀어보는 JavaScript의 원리

COUPANG

www.coupang.com

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.

이전 글에서는 공 하나가 화면 안에서 생각없이 튕기는 내용이었다.

 

 

Javascript 캔버스에서 움직이는 공 그리기

유튜브에서 브라우저에서 각종 도형의 움직임을 Javascript로 구현하는 걸 보고 반해서 바로 공부를 시작. 이제서야 조금씩 머리속에서 그림이 그려져가고 있다. bouncing-ball-3.bryanko555.repl.co 이건 그

mrkool.tistory.com

이번에는 컴퓨터의 강점을 살려, 공의 갯수를 확 늘리게되면 이제 좀 볼만해진다.

이전에는 ball 클래스를 만들어놓고 공을 하나만 만들었었는데, 이번에는 초기화 함수, init()에서 공을 여러개 생성하고,

animate() 함수에서 그 공들을 매 순간 업데이트 할 때마다 계산해준다.

 

function init(){ // 공의 갯수만큼 공의 객체 생성

  for(i=0;i<ballNumber;i++){

    balls[i] = new Ball(canvas.width*0.5, canvas.height*0.5)

   }

}

 

function animate(){ // 매 프레임마다 벌어지는 일들

   ctx.fillStyle='rgba(255,255,255,0.8)'; // 전체 화면 지우기. 하얀색의 alpha값을 변경함에 따라 공의 잔상이 달라진다.

   ctx.fillRect(0,0,canvas.width,canvas.height);

   for(i=0;i<ballNumber;i++){

      balls[i].update();

      balls[i].draw();

    }

}

 

위 데모에서는 30개의 공을 그렸는데, requestAnimationFrame()이 1초에 60번 동작하고, 그 때마다 30개 공의 좌표를 이동시키고 그림을 그리고 벽에 닿았는지 판단해준다. 문돌이에게는 놀라운 일이다. ㅎ

 

다양성을 주기 위해서 각 공마다 랜덤 특성을 부여했다.

시작할때 공이 랜덤한 방향으로 날라가고,

    this.angle = (Math.random()*(Math.PI*2));

공의 색깔이 랜덤하게 생성되고, 

    this.c = 'rgba('+Math.random()*255+','+Math.random()*255+','+Math.random()*255+')';

공의 크기를 다르게 하고,

    this.size = 10+Math.random()*20;

공이 날라가는 속도를 다르게 했다. 

    

this.power = Math.random()*3+2;

 

아예 공의 수를 200개로...

 

이 공이 움직이는 것처럼 보이는 이유가, 매 프레임마다 캔버스 전체를 하얀색 상자를 그려서 덮어버리고 각 공들을 이동한 좌표로 그려주고, 다음 프레임에서 다시 다 지우고, 공들을 다시 그리고 ... 이렇게 반복함으로써 공들이 움직이는 것처럼 보이는 것인데...

캔버스를 덮을 하얀색의 투명도(alpha값)을 다르게 함에 따라서 공의 궤적을 다르게 볼 수 있다.

alpha =1 

alpha = 0.5

alpha = 0.1

 

전체 코드 :

window.onload = function () {
 
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = window.innerWidth-15;
  canvas.height = window.innerHeight-15;
 
  balls=[];
  ballNumber = 20;
 
  class Ball{
    constructor(x,y){ // ball의 기본 속성들을 정의 
      this.x = x;
      this.y = y;
      this.c = 'rgba('+Math.random()*255+','+Math.random()*255+','+Math.random()*255+')'// 공의 색깔을 random으로 설정
      this.size = 10+Math.random()*20;
      this.angle = (Math.random()*(Math.PI*2));
      this.power = Math.random()*3+2;
      this.directionX = this.power * Math.cos(this.angle);
      this.directionY = this.power * Math.sin(this.angle);
    }
    update(){ // 프레임마다 속성들을 변화시킴 
      this.y += this.directionY; 
      this.x += this.directionX;
      if(this.y+this.size>canvas.height || this.y-this.size<0){ // 바운드 처리
        this.directionY *= -1;
        }
      if(this.x>canvas.width-this.size ) {
        this.x = canvas.width-this.size;
        this.directionX *= -1;
      } else if (this.x-this.size < 0){
        this.directionX *= -1;
      }
    }
    draw(){ // 넘어온 속성값대로 캔버스에 그림을 그려줌
       ctx.fillStyle= this.c;
       ctx.beginPath();
       ctx.arc(this.x, this.y, this.size, 0, Math.PI*2true);
       ctx.closePath();
       ctx.fill();
       ctx.lineWidth = 2;
       ctx.strokeStyle= 'black';
       ctx.stroke();
    }
  }
 
  function init(){ // 공의 갯수만큼 공의 객체 생성
      for(i=0;i<ballNumber;i++){
        balls[i] = new Ball(canvas.width*0.5, canvas.height*0.5)
      }
  }
 
  function animate(){ // 매 프레임마다 벌어지는 일들
    ctx.fillStyle='rgba(255,255,255,0.1)'// 전체 화면 지우기. 하얀색의 alpha값을 변경함에 따라 공의 잔상이 달라진다.
    ctx.fillRect(0,0,canvas.width,canvas.height);
    for(i=0;i<ballNumber;i++){
      balls[i].update();
      balls[i].draw();
    }
 
    window.addEventListener('resize',function(){ // 화면 크기가 변하면 캔버스 크기도 변경해줌
      canvas.width=window.innerWidth;
      canvas.height=window.innerHeight;
    })
    requestAnimationFrame(animate);
  }
 
  init();
  animate();
 
  }
cs

다음에는 중력까지 가미해보자.

 

 

자바스크립트를 깨우치다:객체로 풀어보는 JavaScript의 원리

COUPANG

www.coupang.com

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.

지난 글에서는 공이 상자안을 끝없이 튕기는 것을 그려봤는데, 이번에는 중력값을 넣어서 실제에서 공이 튕기는 것처럼 해보자. 

실제에서는 공이 튕기는 힘이 조금씩 줄어들고,

좌우로 움직일때도 속도가 줄어들어야 한다.

실제에서 공이 자유낙하를 한다면 아래와 같을 것이다.

중력이 있기 때문에 단위시간에 떨어지는 거리가 달라지고(0.2, 0.4, 0.6...)

반발해서 올라갈 때는 그와 반대로 올라갈 수록 단위시간의 이동거리가 줄어든다.

이것을 코딩에 반영해줘야 한다.

 

중요한 부분은 update() 이다.

아래에서는 gravity 변수가 매 프레임마다 0.2씩 더 늘어나게 해줬다.

그리고 바운드 될 때마다 gravity에 -1을 곱해서 움직이는 방향을 바꿔준다.

그런데, 바운드 될때마다 다시 올라가는 높이가 이전보다는 낮아져야 되기 때문에 -0.9로 수치를 정했다.

이 수치를 -1.1 이렇게 바꾸면 바운드 되서 이전보다 더 높이 올라간다.

 

x방향도 마찬가지로, 처음에는 5만큼 늘어나지만, 그 다음부터는 0.995를 곱해서 점점 늘어나는 속도가 줄어든다.

좌우 벽에 닿으면 -1을 곱해서 방향이 바뀐다. 

window.onload = function () { // 스크립트 로딩이 완료된 후부터 내용을 시작
 
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
 
  class Ball{
    constructor(x,y){ // ball의 기본 속성들을 정의 
      this.x = x; // x좌표
      this.y = y; // y좌표 
      this.c = 'red'// 시작할때 공의 색깔
      this.size = 20// 공의 반지름
      this.power = 5// 공의 움직임 세기
      this.gravity = this.power; // 공이 상하로 움직이는 값
      this.directionX = 5; // 공이 좌우로 움직이는 값
    }
 
    update(){ // 프레임마다 속성들을 변화시킴 
      // y값의 변동을 계산
      this.y += this.gravity; 
      this.gravity += 0.2 // 중력 값
      console.log(this.gravity)
      if(this.y+this.size >= canvas.height || this.y-this.size <= 0){ // 상하 바운드 처리
      this.gravity *= -0.9// 상하에 닿으면 방향을 전환
      }
 
      // x값의 변동을 계산
      this.x += this.directionX;
      this.directionX *= 0.995;
      if(this.x+this.size > canvas.width || this.x-this.size<0){
      this.directionX *= -1;
      }
    }
 
    draw(){ // 넘어온 속성값대로 캔버스에 원을 그려줌
       ctx.fillStyle= this.c;
       ctx.beginPath();
       ctx.arc(this.x, this.y, this.size, 0, Math.PI*2true);
       ctx.closePath();
       ctx.fill();
    }
    }
 
  function init(){ // 그려질 공의 초기 좌표 설정
      ball1 = new Ball(canvas.width*0.5, canvas.height*0.5)
  }
 
  function animate(){ // 매 프레임마다 벌어지는 일들
    ctx.fillStyle='rgba(255,255,255,0.5)';
    ctx.fillRect(0,0,canvas.width,canvas.height); // 캔버스 전체를 색칠해서 내용을 지워준다
    ball1.update(); // ball1의 좌표 등을 업데이트 한다
    ball1.draw(); // 업데이트된 내용으로 ball을 새로 그린다.
    requestAnimationFrame(animate);
  }
 
  init(); // 공의 초기 좌표를 설정하고, 
  animate(); // 프레임마다 공을 그려준다. 
 
  }
cs

 

혹시 몰라서 html 부분,

<body>
   <canvas id="canvas">   </canvas>
</body>
cs

 

이제 여기에서 배열을 사용해서 공의 수를 확 늘려주면, 좀 볼만한 그림이 나온다.

다음 시간에...

 

 

 

자바스크립트를 깨우치다:객체로 풀어보는 JavaScript의 원리

COUPANG

www.coupang.com

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.

유튜브에서 브라우저에서 각종 도형의 움직임을 Javascript로 구현하는 걸 보고 반해서 바로 공부를 시작.

이제서야 조금씩 머리속에서 그림이 그려져가고 있다. 

 

브라우저 안에서 계속 튕기는 공

bouncing-ball-3.bryanko555.repl.co

 

이건 그냥 단순하지만, 여기에다 배열과 반복이 더해지면 그럴듯한 그림이 그려진다. 

bouncing-ball-1.bryanko555.repl.co

 

오늘은 첫번재 기본적인 바운드 되는 내용부터 정리한다. 

html에 canvas를 설정하고, css에서 화면 가득 채우는 건 기본.

바로 Javascript로 넘어가자.

window.onload = function () { // 스크립트 로딩이 완료된 후부터 내용을 시작
 
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  class Ball{
    constructor(x,y){ // ball의 기본 속성들을 정의
      this.x = x;
      this.y = y;
      this.c = 'rgba('+Math.random()*255+','+Math.random()*255+','+Math.random()*255+')'
// 시작할때 공의 색깔을 랜덤하게 설정. 이거 많이 사용하게 된다.
      this.size = 20// 공의 반지름
      this.angle = (Math.random()*(Math.PI*2)); // 공이 출발할 각도
      this.power = 5// 공의 세기
      this.directionX = this.power * Math.cos(this.angle); // 공이 좌우로 움직이는 값
      this.weight = this.power * Math.sin(this.angle); // 공이 상하로 움직이는 값
    }
    update(){ // 프레임마다 속성들을 변화시키는 함수
      this.y += this.weight; // y값을 계속 증가/감소 시킨다
      this.x += this.directionX; // x값을 계속 증가/감소 시킨다.
 
     
      if(this.y+this.size>canvas.height || this.y-this.size<0){ // 상하 바운드 처리
        this.weight *= -1// 상하에 닿으면 방향을 전환
      }
      if(this.x>canvas.width-this.size || this.x-this.size < 0) { // 좌우 바운드 처리
        this.directionX *= -1// 좌우에 닿으면 방향을 전환
      }
    }
    draw(){ // 넘어온 속성값대로 캔버스에 원을 그려주는 함수
       ctx.fillStyle= this.c;
       ctx.beginPath();
       ctx.arc(this.x, this.y, this.size, 0, Math.PI*2true);
       ctx.closePath();
       ctx.fill();
    }
  }
 
  init = () => { // 그려질 공의 개체를 설정하는 함수
      ball1 = new Ball(canvas.width*0.5, canvas.height*0.5)
  }
  function animate(){ // 매 프레임마다 그림을 새로 그려주는 함수
    ctx.fillStyle='rgba(255,255,255,0.5)'; // 매 프레임마다 캔버스를 통째로 칠하는 색깔. 맨 마지막의 alpha값에 따라 공의 잔상이 남는 정도가 달라진다.
    ctx.fillRect(0,0,canvas.width,canvas.height); // 캔버스 전체를 색칠해서 내용을 지워준다
    ball1.update(); // ball1의 좌표 등을 업데이트 한다
    ball1.draw(); // 업데이트된 내용으로 ball을 새로 그린다.
    window.addEventListener('resize',function(){ // 화면 크기가 변하면 캔버스 크기도 변경해줌
      canvas.width=window.innerWidth;
      canvas.height=window.innerHeight;
    })
    requestAnimationFrame(animate);
  }
  init(); // 공의 초기 좌표를 설정하고,
  animate(); // 프레임마다 공을 그려준다.
 
  }
cs

구조를 간단히 말하자면, 

  ] 그려질 ball의 기본 속성을 정하는 부분 : Ball.constructor(x,y)

  ] ball이 움직일 때마다 x,y 값을 변경해주고, 테두리에 부딪치면 -1을 곱해서 반대로 움직이게 하는 부분 : Ball.update()

  ] 생성된 ball들을 canvas에 그려주는 부분 : Ball.draw()

  ] 위의 함수들을 약 1초에 60번씩 호출해서 반복시키는 부분 : animate()

 

위의 것이 기본적인 구조이고, 저 구조 안에서 공의 개수를 늘리고, 중력값을 주고, 색깔을 변하는 등의 변화를 주면 점점 그럴듯한 그림으로 변해간다. 

앞으로 계속 변형을 늘려가보자.

 

 

자바스크립트를 깨우치다:객체로 풀어보는 JavaScript의 원리

COUPANG

www.coupang.com

 

이 다음에는 공을 여러개로 늘려서 움직여보자.

 

Javascript 캔버스에서 튕기는 공들 (w/o 중력)

이전 글에서는 공 하나가 화면 안에서 생각없이 튕기는 내용이었다. Javascript 캔버스에서 움직이는 공 그리기 유튜브에서 브라우저에서 각종 도형의 움직임을 Javascript로 구현하는 걸 보고 반해

mrkool.tistory.com

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.

코딩을 배우면서 제일 먼저 만드는 프로그램이, 로또번호 만들기, 그리고 계산기인 것 같다. 

 

아래의 계산기는 정말 짧은 소스로 구현을 할 수 있었다. 

 

일반적으로 계산기를 구현하려면, 계산기의 버튼 하나하나에 이벤트를 걸고 거기서 얻은 값을 연산하는 방법인데,

소개할 소스는 아래와 같다. 정말 짧지 않은가?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
window.addEventListener("load",function(){
  var section=document.querySelector(".main");
  var numberPad=section.querySelector(".number-pad");
  var disPlay=section.querySelector(".display");
 
  numberPad.onclick=function(e){
    console.log(e.target.id);
    var key = e.target.id;
    if (key == "equal") {
      console.log(disPlay.value);
      disPlay.value=eval(disPlay.value)} else 
      if (key == "clear") {
        disPlay.value="";} else{
    disPlay.value+=key;}
  }
})
 
cs

 

구현한 계산기는 ↓

bryancalc.netlify.app/

문과적으로 설명하겠다.

 

일단 HTML에서 계산기의 형태를 꾸며주자.

input 입력창과 16개 셀의 테이블로 이뤄져 있다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<section class="main">
    <input type="text" class="display" value="" disabled>
    <table class="number-pad">
      <tr >
        <td id="1">1</td>
        <td id="2">2</td>
        <td id="3">3</td>
        <td id="-">-</td>
      </tr>
      <tr>
        <td id="4">4</td>
        <td id="5">5</td>
        <td id="6">6</td>
        <td id="*">*</td>
      </tr>
      <tr>
        <td id="7">7</td>
        <td id="8">8</td>
        <td id="9">9</td>
        <td id="/">/</td>
      </tr>
      <tr>
        <td id="clear">C</td>
        <td id="0">0</td>
        <td id="equal">=</td>
        <td id="+">+</td>
      </tr>
    </table>
  </section>
cs

CSS는 개인취향대로 꾸며주자. 여기서 그게 중요한건 아니니까.

코드를 설명하자면,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
window.addEventListener("load",function(){
  var section=document.querySelector(".main");
  var numberPad=section.querySelector(".number-pad");
  var disPlay=section.querySelector(".display");
 
  numberPad.onclick=function(e){
    console.log(e.target.id);
    var key = e.target.id;
    if (key == "equal") {
      console.log(disPlay.value);
      disPlay.value=eval(disPlay.value)} else 
      if (key == "clear") {
        disPlay.value="";} else{
    disPlay.value+=key;}
  }
})
 
cs
#1:소스가 loading이 완료된 이후에 스크립트를 실행. 여기서는 별 의미 없음
#2:html에 main클래스(계산기 전체)를 변수에 넣음
#3:html에 number-pad클래스(숫자패드)를 변수에 넣음 
#4:html에 display클래스(숫자를 보여주는 input창)를 변수에 넣음
#6:숫자패드의 어딘가가 클릭되면 이하의 함수를 실행
#7:e.target.id를 중간확인하려고 넣었음. target은 클릭된 개체를 말한다. 
   그래서 target.id라고 하면 클릭된 위치의 id를 알 수 있다.
   html의 각 숫자에 id를 부여했었다.
#8: 클릭된 위치의 id를 key변수에 할당.
    5의 셀을 클릭하면 그 위치의 id인 '5'가 key에 들어간다.
#9: key가 'equal'('='셀)이면 이하의 코드를 실행한다.
#11: input창에 입력된 내용을 계산해서 disPlay.value에 넣어준다.
     여기서 eval이란 함수가 input창에 텍스트 형식의 숫자와 연산자들을 그대로 계산해줘버린다. 와우!!
#12,13: 'clear'('C'셀)이 클릭됬으면 input창의 값을 빈칸으로 만든다. 
#14: 위 두 경우가 아니라면, 클릭된 셀의 id들(숫자,연산자)를 그대로 input창에 계속 붙여준다.
 
각 숫자에 일일이 이벤트를 붙이지 않고 짧게 가능한 이유가, 숫자 전체에 하나의 이벤트를 걸어놨는데, 그 아래의 셀에서 클릭된 셀의 id를 반환받을 수 있는 덕분이다. e.target.id
이걸 '버블링'이라고 하는데, 아래 강좌영상에서 배웠다.
 
 
마치기 전에, 
위 링크와 같은 훌륭한 강좌 동영상이 1년반이 넘도록 조회수가 1,400회 조금 넘었을 뿐이다. 이 강좌의 배경지식 수준, 강좌를 준비한 수고를 생각하면 너무 아깝다. 
그런데, 일례로, 배틀그라운드 기초소개 동영상 조회수는 85만회... ㅎㅎ
그래 행복은 성적순이 아닌거다. ㅎ

 

 

 

명품 HTML5 + CSS3 + Javascript 웹 프로그래밍

COUPANG

www.coupang.com

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.

+ Recent posts