오늘의 삽질

RogueLike TextGame

서버 연결

  • node.js 강의에 있던 MongoDB를 이용하여 무기들이나, 점수 랭킹 등을 저장하고 불러오는 기능을 구현해볼 것이다!
  1. MongoDb_Atlas(Cloud Service) 가입

    1. 구글/깃허브를 통해 로그인!

    2. New project를 눌러 M0(free)를 선택하고, Region(나라) 는 서울로 설정한다음 이름을 정해 저장해준다!

    3. Database Access 에서 ADD NEW DATABASE USER 로 이름과 비밀번호(나중에 사용할 예정이니 쉽게 해두기!)를 설정하고 만들어준다!
      (Built-in Role 은 Atlas admin 으로!)

    몽고DB유저추가

    => 마지막으로 Network Access 에 들어가 모든 사용자가 접속할 수 있도록 IP ADDRESS는 0.0.0.0 으로 설정해준다!

  2. mongoose Package 설치

    • yarn(npm) 을 이용하여 mongoose 를 프로젝트에 설치해준다! (VS Code 터미널)
     yarn add mongoose
    
    • 내 MongoDB와 연결하기 위한 함수를 만들어준다!
     import mongoose from 'mongoose';
    
     const connect = () => {
         mongoose
             .connect(
                 'mongodb+srv://<db_username>:<db_password>@express-mongo.p1x5z.mongodb.net/?retryWrites=true&w=majority&appName=express-mongo',
                 {
                     dbName: 'roguelike', // 데이터베이스명 입력!(맘대루)
                 },
             )
             //에러 탐지1!
             .catch((err) => console.log(err))
             .then(() => console.log('데이터 불러오기 성공'));
     };
    
     //에러 탐지2!
     mongoose.connection.on('error', (err) => {
         console.error('데이터 불러오기 실패', err);
     });
    
     export default connect;
    

    => 여기서 mongooseconnect() 의 첫번째 인수를 얻기 위해 아래 사진과 같이 Overview 에서 Connect를 누르고 Drivers를 선택한다!

    몽고DB서버연결

    => Driver는 현재 프로젝트에 쓰고있는 Node.js를 선택하고 버전도 맞춰 아래에 있는 코드를 복사하여 에 유저 생성 때 쓴 비밀번호를 입력해주고 넣으면 된다! (`<>` <= 이것도 지워요..)

    몽고DB서버연결2

  3. 데이터 저장!

    1. mongoose를 이용해서 데이터 저장 형식을 지정해준다!

       import mongoose from "mongoose";
       const weaponSchema = new mongoose.Schema({
       // 이름
       name: {
           type: String,
           required: true,
           uinque: true,
       },
       //기본 데미지
       damage: {
           type: Number,
           required: true,
       },
       //회복량
       heal: {
           type: Number,
           required: true,
       },
       //무기 스탯 영향 0: 이해력 / 1: d-day / 2: 최대 정신력 / 3: -이해력
       type: {
           type: Number,
           required: true,
       },
       //등급
       rating: {
           type: String,
           required: true,
       })
       export default mongoose.model("WeaponSch", weaponSchema)
      
      • 여기서 type은 들어올 값을 지정해주는 것이고, required는 이 값이 무조건 들어와야하는지 여부를, uinque는 중복값이 허용되지 않게 하는 설정이다!

      => 추가로 default를 이용해 값이 없을 시 기본 값을 설정해 줄 수 있다!

    2. mongoDB 서버에 연결 후, 데이터를 올려 볼 것이다!

       //서버 연결 함수(위에)
       import connect from './storage/index.js';
       //올릴 데이터 형식(바로 위)
       import WeaponSch from './storage/weapon-schemas.js';
       //올릴 데이터를 저장한 곳
       import Weapons from './storage/weapons.js';
      
       //서버 연결!
       connect();
      
       //데이터를 모두 돌면서 WeaponSch 형식으로 인스턴스를 만든다
       Weapons.forEach(async (val) => {
           const newWeapon = new WeaponSch({
               name: val.name,
               damage: val.damage,
               heal: val.heal,
               type: val.type,
               rating: val.rating,
           })
      
           // 만든 WeaponSch 인스턴스를 서버에 저장!
           await newWeapon.save()
       })
      
    3. 값 확인하기

      • Atlas 사이트(MongoDB Cloud Service) 에서 Clusters 에 들어가 Browse Collections 를 누른다!

      값확인1

      => 여기서 보고 싶은 database 명을 누르고 데이터 형식을 찾아 값을 확인한다!

      값확인2

      => 잘 들어온 걸 확인할 수 있었다!

  4. 데이터 불러오기!

    • 저장은 한번만 해두면 DB에서 계속해서 꺼내오면 된다! 그렇다면 어떻게 꺼내오면 될까?
    1. mongoose 메서드 모델명(형식).find()를 통해 전체 값을 가져오기!

       Model.find(conditions, [projection], [options], [callback])
       // conditions = 문서와 일치시키기 위한 조건
       // [] = 선택사항
       // projection = 제외목록 지정
       // options = 정렬 또는 제한 조건
      

      => 데이터를

    2. 모델명(형식).findone() 으로 데이터 하나만 가져오기!

       Model.findOne(query, [projection], [options], [callback])
       // query = 조건 
       // [] = 선택사항
       // projection = 제외목록 지정
       // options = 정렬 또는 제한 조건
      

    => 이런 방식으로 처음 화면에서 weapons를 가져와 쓸 수 있게 하면 되게따!

     //weapons.js 무기들 가져오기
     import Weapon from '../class/weapon.js';
     import connect from './connect.js';
     import weaponSch from './weapon-schemas.js';
    
     connect()
    
     const Weapons = [];
    
     const temp = await weaponSch.find().exec();
    
     temp.forEach((val) => {
         Weapons.push(new Weapon(
             val.name,
             val.damage,
             val.heal,
             val.rating
         ))
     })
    

관리자 모드 만들기

관리자모드

  • 무기들을 새로 만들거나 모든 무기를 보려면, 이 데이터들을 읽고/쓰기 위한 코드들을 관리자인 나만 쓸 수 있게 만들려 한다!
  1. admin 파일 만들기 + DB에 password Collection 연동

    • 관리자 모드를 이벤트 파일에 만들어주고, 이동 시, 비밀번호를 물어봐 데이터베이스에서 가져온 값과 비교해 틀릴경우 메인화면으로 돌아가게 설계를 하였다!
     const admin = async (password) => {
         //데이터 저장공간에서 가져오기
         const passwordCk = await mongoose.model('password', PSWDSchema).findOne({ name: "admin" }).exec()
         // 값이 맞는지 확인 후 아닐경우 return start로 메인화면 이동
         if (passwordCk.password !== password) {
             console.log(chalk.redBright('오류! 비밀번호가 틀렸습니다!!'))
             console.log(chalk.yellowBright('메인화면으로 이동합니다!'));
             readlineSync.question('Back<<')
             return start()
         }
     }
    
    • 관리자 모드에서 할 수 있는 일로 무기 추가하기 / 무기 전체 확인 / 이름 찾아 확인 기능을 넣을 예정이다!
    1. 무기 List 확인

      • 등급별로 정렬해서 보이게 하기 위해 ratingToNum 함수를 따로 만들었다!
       //무기 전부 조회
       const WeaponList = (logs) => {
           Weapons.sort((a, b) => {
               const x = ratingToNum(a)
               const y = ratingToNum(b)
               return y - x
           }).forEach((val, idx) => {
               if (idx === 0) {
                   logs.push(
                       chalk.magentaBright(
                           `=========== ${val.rating}급 ===========`,
                       ),
                   );
               } else if (val.rating !== Weapons[(idx - 1)].rating) {
                   logs.push(
                       chalk.magentaBright(
                           `=========== ${val.rating}급 ===========`,
                       ),
                   );
               }
      
               logs.push(
                   chalk.yellowBright(
                       `| 등급 : ${val.rating} | 몰입도 : ${String(val.damage).padStart(3, " ")} Page | 수면효과 : ${String(val.heal).padStart(3, " ")} | 이름 : ${val.name}`,
                   ),
               );
           })
       }
       // 정렬용 비교 함수
       const ratingToNum = (x) => {
           switch (x.rating) {
               case "H":
                   return 7
               case "S":
                   return 6
               case "A":
                   return 5
               case "B":
                   return 4
               case "C":
                   return 3
               case "D":
                   return 2
               case "E":
                   return 1
           }
       }
      
    2. 무기 이름을 받아 List에서 똑같은 이름을 찾아 반환해주는 함수를 만들어야겠다!

      • 간단하게 금방 만들었는데… 작동이 안됀다?!

      => 콘솔에 직접 값을 찍어봤는데 입력받은 한글이 깨져서 나온 것이다!

       //무기 하나 조회
       const SearchingWeapon = (name, logs) => {
           const val = Weapons.find((e) => { return e.name === name })
      
           if (val) {
               logs.push(
                   chalk.yellowBright(
                       `| 등급 : ${val.rating} | 몰입도 : ${String(val.damage).padStart(3, " ")} Page | 수면효과 : ${String(val.heal).padStart(3, " ")} | 이름 : ${val.name}`,
                   ),
               );
           } else {
               logs.push(chalk.redBright('입력하신 이름을 가진 무기를 찾을 수 없습니다!'));
           }
       }
      
      • 한글 글자 깨짐 현상 고치기

      => 알아본 바 chcp 65001라는 터미널 명령어를 통해 코드 페이지를 UTF-8로 변경해서 유니코드 문자(한글 포함)를 지원하게 만들어 준다고 한다!(1회용)

      => 명령어를 게임 시작전마다 실행하게 만들어 주기 위해,
      터미널 명령어를 코딩으로 입력시키는 함수를 찾게 되었다!

       import { execSync } from 'child_process';
       //터미널에 직접 실행
       execSync('chcp 65001')
      

      => 게임 시작부터 실행하게 만들어 주니, 생각했던 기능들이 잘 작동한다!

    3. 무기 추가기능 넣기!

      • 위에 데이터를 저장하는 기능을 이용해 새로운 무기를 만들 수 있도록 할 것이다!
       console.log(chalk.yellowBright(
           `기입 사항들을 (,) 기준으로 차례대로 입력해주세요!`,
       ))
       newWp = readlineSync.question('무기 정보 기입::').split(",");
      
       console.log(chalk.yellowBright(
           `입력된 정보 ${newWp}`,
       ))
       const newWpData = new WeaponSch({
           name: newWp[0],
           damage: Number(newWp[1]),
           heal: Number(newWp[2]),
           type: Number(newWp[3]),
           rating: newWp[4],
       })
      
       await newWpData.save()
           .catch((err) => console.log(err))
           .then(logs.push(chalk.greenBright('정상적으로 저장되었습니다!')))
      

      => 잘 작동한다!

  2. 메인 화면에 연결하기

    • 메인 화면에 선택지를 만들어 주고, 거기에 간단하게 연결하였다!
     case '3':
         // 무기 정보 확인 및 추가 및 삭제 
         console.log(chalk.redBright('관리자 모드 진입을 위해 비밀번호를 입력해주세요'));
         //관리자 모드 함수
         admin(readlineSync.question('비밀번호:'));
         break;
    
  3. 단일로 검색한 무기를 수정하거나 삭제하는 기능도 추가하자!

    • 삭제 기능 구현! (+ 무기값 update 기능도 구현)
     await WeaponSch.deleteOne({ name: name })
     .then(async () => {
         logs.push(
             chalk.redBright(
                 `무기를 삭제했습니다!`,
             ),
         );
         await update()
     })
     //무기 업데이트 함수
     const update = async () => {
     //초기화
     Weapons.splice(0, Weapons.length);
     const temp = await WeaponSch.find().exec()
     //업데이트
     temp.forEach((val) => {
         Weapons.push(new Weapon(
             val.name,
             val.damage,
             val.heal,
             val.rating,
             val.type,
         ))
     })
     }
    
    • 무기 수정 구현
     const valueName = readlineSync.question('바꿀 정보를 영어로 입력해주세요! ');
     console.log(chalk.yellowBright(`${valueName}을 선택하셨습니다.`))
     const value = readlineSync.question('값을 입력해주세요!');
     console.log(chalk.yellowBright(`${value}를 입력 하셨습니다.`))
    
     const weaponFound = await WeaponSch.findOne({ name: name }).exec()
     switch (valueName) {
         case "name":
             weaponFound.name = value
             break;
         case "rating":
             weaponFound.rating = value
             break;
         case "damage":
             weaponFound.damage = Number(value)
             break;
         case "heal":
             weaponFound.heal = Number(value)
             break;
         case "type":
             weaponFound.type = Number(value)
             break;
         default:
             console.log("오류입니다")
             break;
     }
     //저장
     await weaponFound.save();
     await update()
    

개선점 분석

  • 내일 할 일을 적어 두어서 다음 날 확인해가며 시작하는게 많이 편했다!

  • 내일은 플레이어 랭킹 시스템 구현 + 리팩토링/밸런싱 을 마지막으로 이번 과제를 마무리할 것 같다!