과제
- 자바스크립트로 오목게임 만들어보기
- null과 undefined의 차이점을 찾아보기
오목게임 만들기
처음 과제가 주어졌을때 내가 잘 못 보았나 싶었다. 과제가 다들 쉽다 이야기 하여 난이도 높은 과제를 준다고 하였는데, 누군가 쉽다고 한사람이? 아무튼 엄청난 문제에 직면하게 되었고, 이에 출제자의 의도를 생각해보기로 했다. '아예 백지를 제출하고, 점차 발전하는 모습을 보여주는 것을 원하는게 아닐까' 라는 잠깐의 생각을 해보았지만 일단 작성해보기로 했다.
조건
- Nodejs와 함께 콘솔창에서 실행되도록 사용자 입출력 도구를 사용한다.
- 오목판 사이즈는 30x30으로 고정한 후 정사각형의 형태의 오목판을 만든다.
- 사용자 입력 도구에 좌표값 (15,15)라고 입력하여 바둑돌을 둔다.
- 흑은 1로, 백은 0으로 표기하여 화면에 흑과 백이 번갈아가면서 두도록 입력 도구가 계속 뜨도록 입력 받는다.
- 오목 규칙에 따라 5줄이 먼저 완성되면 “Game over”와 같이 누가 이겼는지 승패를 알리는 출력을 만든다.
- 승패가 계속 나지 않을 경우 실행 후 5분이 지나면 자동 종료시킨다.
기본 설정
유저의 입력을 받을 수 있도록 readline 모듈을 불러왔다. 해당 모듈에 대해서는 "readline 모듈 사용하기", leenzy.log, 를 참고 하였다.
const readline = require('readline');
function getInput() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question(`${Player === BLACK ? '흑' : '백'}의 차례입니다. (x, y) 값을 입력하세요 (예: 15, 15): `, (input) => {
rl.close();
handleInput(input);// 올바른 입력인지 확인하는 내용 추가 예정
});
}
오목판의 사이즈는 30*30 의 사이즈 를 만들기 위해 SIZE 변수에 30, 돌의 위치를 기록하기 위한 Black 과 White, Empty에는 각각 1, 0, -1 을 지정해 주었다. 돌이 5개 모여야 이기기 때문에 win_count를 5로 설정 하였다. 해당 값들은 변하면 안되기 때문에 const로 선언해주었다.
const SIZE = 30;
const BLACK = 1;
const WHITE = 0;
const EMPTY = -1;
const WIN_COUNT = 5;
const TIMEOUT = 5 * 60 * 1000;
바둑판과 플레이어의 경우 값이 변할 수 있어야 되기에 let을 통하여 지정하였다. 오목의 선공은 검은돌 이라는 규칙이 있기 때문에 플레이어는 검은돌로 시작한다.
let bord = []
let Player = BLACK;
플레이어 색상 변환
삼항연산자
돌을 놔두게 될 경우, Player의 색상에 따라 스위치 하기 위해 삼항연산자를 이용하기로 하였다. 기존의 Player가 BLACK 일 경우 WHITE, 그렇지 않을 경우 BLACK을 Player 값으로 지정하도록 작성하였다.
Player = Player === BLACK ? WHITE : BLACK;
Player * -1
돌을 놔둘때 플레이어 값에 -1 을 곱한다면 삼항연산자를 쓰지 않고 처리할 수 있지 않을까 라는 생각이 작업이 끝나고 들었다. 이 경우 처음에 설정한 BLACK, WHITE, EMPTY 값을 각각 아래와 같이 수정하여 새롭게 작업하면 될 것 같다는 생각이 들었지만, 일단은 2.2.1의 코드로 작성하였다.
const BLACK = 1;
const WHITE = -1;
const EMPTY = 0;
Player = Player * -1
0 = false
작성하고 보니 WHITE 값이 0 이란걸 깨달았다. 그렇게 되면 아래와 같이 쓰는게 낫지 않을까 란 생각을 나중에 하였다. 0 값은 false 이기에, 상단의 코드를 하단처럼 변경하더라도 문제가 없을거란 생각을 하였지만 일단 코드 수정은 하지 않았다.
Player = !player ? BLACK : WHITE ;
오목판 만들기
바둑돌을 올려둘 오목판을 만드는 작업을 진행하고자 1. bord 를 0부터 SIZE 크기와 같아지기 전(30)까지 비어있는 값으로 늘려주었다. 2. 생성된 bord 값에 SIZE 크기와 같아지기 전까지 EMPTY 값(-1)을 할당하였다.
for (let i = 0; i < SIZE; i++) {
bord[i] = []; // #1
for (let j = 0; j < SIZE; j++) {
bord[i][j] = EMPTY; }}} // #2
// console.log(#1)
bord = [
[], [], [], [], [], [], [], [], ... , [], []
]
// console.log(#2)
[
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
...,
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
]
오목판 출력
오목판을 만들었으면, 출력을 해주어야 되지 않을까? 라는 생각을 바탕으로 오목판을 출력하기 위해 작업을 진행하기로 하였다. 3. 작업에 앞서 콘솔창 초기화 작업을 먼저 실행하여, 오목을 진행할때 깔끔한 콘솔 상태를 유지하고자 하였다.
오목판의 맨 윗줄에 숫자를 출력하여, 사용자에게 좌표를 알기 쉽도록 도움을 주고자 하였다. 맨 처음에 공백을 " " 을 통하여 열과 행이 만나는 지점의 공백을 만들기로 하였다. 4. SIZE로 Array.from 작성하여, 0부터 29까지 순서를 나열하기로 하였으며 5. padStart(2, ' ')을 이용하여 2자리로, 부족한 자리수는 공란을 만들어 늘려주기로 하였다.
6 오목판의 크기만큼 반복문을 이용하여 세로열을 만드는 작업을 진행하였다. 첫 번째 열에서 첫 글자는 기존과 동일하게, i.toString().padStart(2, ' ') 을 이용하여 i번째 줄을 공백을 추가하여 2자리 수로 만들어 주었다. 이후 board의 값을 확인하여, 8 해당하는 값이 BlACK 일 경우 1, WHITE 일 경우 0, 둘다 아닐경우 공란 을 출력하기로 하였다.
console.clear(); // #3
console.log(" " + Array.from({ length: SIZE }, // #4
(_, i) => i.toString().padStart(2, ' ')).join('')); // #5
for (let i = 0; i < SIZE; i++) { // #6
console.log(i.toString().padStart(2, ' ') + ' ' + board[i].map(cell => {
if (cell === BLACK) return ' 1';
if (cell === WHITE) return ' 0';
return ' '; // #8
}).join(''));
}
사용자 입력 검사
9 사용자가 입력한 좌표값을 , 기준으로 나누고, parseInt 함수를 통하여 분리된 좌표값들이 10진수로 변환하여 x와 y에 저장하였다. 10 이후 저장된 x y 값에 여러 조건들을 대입하여 사용자가 올바른 값을 입력했는지에 대해 검사를 진행하여, 오류에 해당하면 다시 입력을 하도록 하였다. 11 만약 입력한 값에 오류가 없다면, 해당 좌표에 사용자 색상을 표시하고 다시 보드판을 불러오게 작업하였다.
function handleInput(input) {
const [x, y] = input.split(',').map(coord => parseInt(coord.trim(), 10)); // #9
if (isNaN(x) || isNaN(y) || x < 0 || x >= SIZE || y < 0 || y >= SIZE) {
console.log("잘못된 입력 : ");
getInput();
return;
}
if (board[x][y] !== EMPTY) {
console.log("이미 존재하는 위치 : ");
getInput();
return;
} // #10
board[x][y] = Player;
printBoard();
} // #11
승리 여부 확인
오목이 완성되었는지 확인하기 위한 함수를 만들기로 하였다. 해당 함수는 사용자가 입력을 할때 실행하여, 해당 위치로부터 오목을 달성하였는지에 대해 확인하고자 하였다. 12 directions 을 선언하여, 수평, 수직, 대각선에 대한 검사를 시도하고자 하였고 검사를 바탕으로 for 반복문을 통하여 연속된 돌의 갯수를 count 하여, 해당 값이 WIN_COUNT 를 넘어가게 된다면 사용자가 승리하였다고 하였다.
13 해당 작업을 반복하여, 좌표가 0보다 작아지거나, SIZE 보다 커지게 된다면, Break를 걸었으며, 14 확인하는 돌의 색상과 다를경우에도 break 를 사용하여 멈추게 하였다. 이후 해당하는 돌의 갯수가 5개 이상 존재한다면, checkWin에 true 값을 반환하고 아니라면 false 값을 반환하게 하였다.
function checkWin(row, col) {
const DIRECTIONS = [[1, 0], [0, 1], [1, 1], [-1, 1]];
for (const [dx, dy] of DIRECTIONS) {
let count = 1; // #12
for (let i = 1; i < WIN_COUNT; i++) {
const newRow = row + i * dx;
const newCol = col + i * dy;
if (newRow < 0 || newRow >= SIZE || newCol < 0 || newCol >= SIZE) break; // #13
if (board[newRow][newCol] !== board[row][col]) break; // #14
count++;
}
for (let i = 1; i < WIN_COUNT; i++) {
const newRow = row - i * dx;
const newCol = col - i * dy;
if (newRow < 0 || newRow >= SIZE || newCol < 0 || newCol >= SIZE) break;
if (board[newRow][newCol] !== board[row][col]) break;
count++;
}
if (count >= WIN_COUNT) return true; // # 15
}
return false; // # 오목이 완성되지 않았으면 false 반환
}
타이머
타임아웃에 대한 내용은 해당 사이트를 참고하여 작성하였다.
setTimeout(() => {
console.log("입력이 없어 종료함");
process.exit(0);
}, TIMEOUT);
결과물
js 첫 과제로 계산기 같은거가 나올거라 생각하였는데... 예상을 뒤엎고 나온 과제에 대해 뭐라고 말해야될까... 이곳 저곳 찾아보기도 하고, 중간 중간에 친구와 팀원의 도움도 받으면서 작성한 까닭에 클론 코딩 하는 느낌으로 만들어버려서 다시 하라고 하면 못 할거 같다. 는게 나의 느낌이다..
친구가 내 코드 보면서 "const 는 대문자로 작성하는거야", "삼항연산자는 가급적이면 안쓰는게 좋아" 라는 말들을 해주었는데, 너무 주먹 구구식으로 작성하여 해당 조언이 반영 되었을 수도 아닐 수 도 있다. 이후에 시간이 난다면, 친구가 말해준 내용을 정리를 해볼까 한다.
그리고 사실 9 부분에 형식만 맞다면 입력값을 많이 집어넣어도 (ex. 숫자, 숫자, 숫자, 숫자) 왜 오류가 안 나는지 아직 모르겠다. (입력값이 적어도 오류가 안나던데, 그 경우는 예외처리로 입력이 안되도록 하였다)
해당 코드는 깃허브 에서 확인이 가능하다. 아마도.
null 과 undefined
null
수가 값이 없다는 것을 명시적으로 나타내는 자바스크립트 데이터 타입 중 하나
null은 의도적으로 값이 없음을 나타냄
변수를 선언하고 null을 할당함으로써, 이 변수는 값이 없음을 의미
let variable = null;
console.log(variable); // null
undefined
변수가 선언되었지만 아무런 값도 할당되지 않은 상태
초기화되지 않은 변수나 존재하지 않는 객체의 속성에 접근하면 undefined가 반환
let variable;
console.log(myVariable); // undefined
null != undefined
console.log(null == undefined); // true
console.log(null === undefined); // false
null 과 undefined 케이스
변수를 선언하고 값을 할당하지 않음 (반환값 undefined)
변수를 선언하고 초기화하지 않으면 해당 변수는 자동으로 undefined로 초기화 됨
변수가 선언되었지만 값이 할당되지 않은 상태를 의미
let variable1;
console.log(variable1 === null); // false
console.log(variable1 === undefined); // true
변수를 선언하고 undefined로 명시적으로 할당 (반환값 undefined)
변수에 명시적으로 undefined를 할당했기 때문에 undefined 값을 가짐
이는 값을 의도적으로 비워두기 위해 사용
let variable2 = undefined;
console.log(variable2 === null); // false
console.log(variable2 === undefined); // true
객체에 존재하지 않는 속성에 접근 (반환값 undefined)
객체에 존재하지 않는 속성에 접근하면 undefined가 반환
let obj = {};
console.log(obj.someProperty === null); // false
console.log(obj.someProperty === undefined); // true
함수에서 반환값을 지정하지 않거나 return 문이 없는 경우 (반환값 undefined)
함수 내 return 문을 사용하지 않으면 함수가 값을 반환하지 않음
이로 인해 호출한 곳에서는 undefined가 반환
function getUndefined() {}
let result1 = getUndefined();
console.log(result1 === null); // false
console.log(result1 === undefined); // true
함수가 명시적으로 undefined를 반환 (반환값 undefined)
함수가 명시적으로 return undefined;를 반환하면 해당 함수의 반환값은 undefined
function returnUndefined() {
return undefined;
}
let result2 = returnUndefined();
console.log(result2 === null); // false
console.log(result2 === undefined); // true
null로 변수를 할당 (반환값 null)
null은 값이 없음을 나타내는 데이터 타입
변수에 null을 할당하면 해당 변수의 값은 null
// Case 6:
let nullVariable = null;
console.log(nullVariable === null); // true
console.log(nullVariable === undefined); // false
본 후기는 정보통신산업진흥원(NIPA)에서 주관하는 <AI 서비스 완성! AI+웹개발 취업캠프 - 프론트엔드&백엔드> 과정 학습/프로젝트/과제 기록으로 작성 되었습니다.