TypeScript에 설치된 Robots를 팩스로 전송했습니다(CUI ver. 없음).

저는 약간 타입 스크립트 느낌으로...

개시하다


지난 기사에서 이뤄진 로보츠.의 타입 스크립트 코드가 너무... 그래서 팩스로 보내봤어요.

분류도

  • 로보츠의 학급 설계를 조금 진지하게 고려했다.

  • 의식적으로 설계MVC 모드.
  • 는 모델들의 로봇급 로봇 동작 등 규칙을 관리하는 논리에 해당한다.
  • 는 뷰가 Field 클래스 터미널에 표시된 화면의 처리를 담당하는 셈이다.
  • 는 Controller가 게임 레벨에서 사용자의 조작을 받아들이고 로보트, Field 레벨과의 협력을 담당하는 것과 같다.
  • robots-cd.png

    Robot 클래스(Model)


    지난번에는 하나였다.ts 파일로 모두 기술했지만 분업 후 여러 파일로 로보츠 게임을 구성했다.
  • 따라서 다른 파일에서 로봇 클래스를 사용할 때 export를 설명하고 다른 파일의 클래스를 참조하여 import을 진행한다.(지난번 설치할 때 이것도 몰랐는데...)
  • 로보트인포형의 배열을 통해 플레이어, 적, 폐기물을 포함한 모든 로봇을 관리한다.
  • 플레이어의 동작, 적의 동작, 고철화 등 로보츠 규칙(논리)에 대한 처리를 여기에 기술한다.
  • robot.ts
    // ロボットのタイプ(プレイヤー、敵、スクラップ)
    export enum RobotType {
        Player,
        Enemy,
        Scrap
    }
    
    // ロボットが動く方向
    export enum RobotMove {
        Teleport,
        Wait,
        Left,
        Right,
        Down,
        Up,
        LowerLeft,
        LowerRight,
        UpperLeft,
        UpperRight,
        Unknown
    }
    
    // 1体のロボットに関する情報 (座標とロボとのタイプ)
    export type RobotInfo = {
        x: number,
        y: number,
        type: RobotType
    }
    
    // ロボットの操作に必要な関数をinterfaceで予め定義
    export interface RobotRules {
        makeRobots(width: number, height: number, level: number): void;
        canPutRobot(x: number, y: number): boolean;
        canMove(x: number, y: number, width: number, height: number): boolean;
        movePlayer(toward: RobotMove, width: number, height: number): boolean;
        moveEnemey(): boolean;
        wipeOut(): boolean;
        countDeadEnemy(): number;
        countTotalDeadEnemy(level: number): number;
    }
    
    // ロボットの管理
    export class Robot implements RobotRules {
        robotList: RobotInfo[];
    
        constructor() {
            this.robotList = [];
        }
    
        // ロボットの初期配置(プレイヤー、敵同時に行う)
        makeRobots(width: number, height: number, level: number): void {
            this.robotList = [];
    
            // 0番目は Player
            let x = Math.floor((Math.random() * width) + 1);
            let y = Math.floor((Math.random() * height) + 1);
            this.robotList.push({ x, y, type: RobotType.Player });
    
            // 1番目から (numOfRobots-1)番目はEnemy
            let count = 0;
            const numOfEnemy = level * 10;
            while (count < numOfEnemy) {
                x = Math.floor((Math.random() * width) + 1);
                y = Math.floor((Math.random() * height) + 1);
                if (!this.canPutRobot(x, y)) {
                    // 同じ場所にロボットを置かない
                    continue;
                }
                this.robotList.push({ x, y, type: RobotType.Enemy });
                count++;
            }
        }
    
        // ロボットの配置ができるかチェック
        canPutRobot(x: number, y: number): boolean {
            // tslint:disable-next-line: prefer-for-of
            for (let i = 0; i < this.robotList.length; i++) {
                if (x === this.robotList[i].x && y === this.robotList[i].y) {
                    return false;
                }
            }
    
            return true;
        }
    
        // プレイヤーロボットが正しく動けるかチェック
        canMove(x: number, y: number, width: number, height: number): boolean {
            // フィールド外に出ないかチェック
            if (x === 0 || y === 0 || x === width + 1 || y === height + 1)
                return false;
    
            // 移動先にスクラップがあるかチェック
            for (let i = 1; i < this.robotList.length; i++) {
                if (this.robotList[i].x === x && this.robotList[i].y === y && this.robotList[i].type === RobotType.Scrap) {
                    return false;
                }
            }
    
            return true;
        }
    
        // プレイヤーロボットの移動
        movePlayer(toward: RobotMove, width: number, height: number): boolean {
            let x = this.robotList[0].x;
            let y = this.robotList[0].y;
            switch (toward) {
                case RobotMove.Wait:
                    break;
                case RobotMove.Teleport:
                    do {
                        x = Math.floor((Math.random() * width) + 1);
                        y = Math.floor((Math.random() * height) + 1);
                    } while (!this.canMove(x, y, width, height));
                    break;
                case RobotMove.Up:
                    y--;
                    break;
                case RobotMove.Down:
                    y++;
                    break;
                case RobotMove.Left:
                    x--;
                    break;
                case RobotMove.Right:
                    x++;
                    break;
                case RobotMove.UpperLeft:
                    x--;
                    y--;
                    break;
                case RobotMove.UpperRight:
                    x++;
                    y--;
                    break;
                case RobotMove.LowerLeft:
                    x--;
                    y++;
                    break;
                case RobotMove.LowerRight:
                    x++;
                    y++;
                    break;
                case RobotMove.Unknown:
                    return false;
            }
            if (!this.canMove(x, y, width, height)) {
                return false;
            }
    
            this.robotList[0].x = x;
            this.robotList[0].y = y;
    
            return true;
        }
    
        // プレイヤーを動かした後に敵を一マス動かす
        moveEnemey(): boolean {
            for (const item of this.robotList) {
                if (item.type === RobotType.Player || item.type === RobotType.Scrap) {
                    continue;
                }
                // プレイヤーの位置に向かうように敵を一マス動かす
                if (this.robotList[0].x === item.x && this.robotList[0].y > item.y) {
                    item.y++;
                } else if (this.robotList[0].x === item.x && this.robotList[0].y < item.y) {
                    item.y--;
                } else if (this.robotList[0].x > item.x && this.robotList[0].y === item.y) {
                    item.x++;
                } else if (this.robotList[0].x < item.x && this.robotList[0].y === item.y) {
                    item.x--;
                } else if (this.robotList[0].x < item.x && this.robotList[0].y < item.y) {
                    item.x--;
                    item.y--;
                } else if (this.robotList[0].x < item.x && this.robotList[0].y > item.y) {
                    item.x--;
                    item.y++;
                } else if (this.robotList[0].x > item.x && this.robotList[0].y < item.y) {
                    item.x++;
                    item.y--;
                } else if (this.robotList[0].x > item.x && this.robotList[0].y > item.y) {
                    item.x++;
                    item.y++;
                }
            }
    
            // 敵同士が衝突したらスクラップにする
            const length = this.robotList.length
            for (let i = 1; i < length - 1; i++) {
                for (let j = i + 1; j < length; j++) {
                    if ((this.robotList[i].x === this.robotList[j].x) && (this.robotList[i].y === this.robotList[j].y)) {
                        this.robotList[i].type = RobotType.Scrap;
                        this.robotList[j].type = RobotType.Scrap;
                    }
                }
            }
    
            // プレイヤーと敵が衝突したらゲームオーバー
            for (let i = 1; i < length; i++) {
                if ((this.robotList[0].x === this.robotList[i].x && this.robotList[0].y === this.robotList[i].y)) {
                    return false;
                }
            }
    
            return true;
        }
    
        // 全滅チェック
        wipeOut(): boolean {
            for (let i = 1; i < this.robotList.length; i++) {
                if (this.robotList[i].type === RobotType.Enemy) {
                    return false;
                }
            }
            return true;
        }
    
        // 倒した敵の数
        countDeadEnemy(): number {
            const length = this.robotList.length;
            let count = 0;
            for (let i = 1; i < length; i++) {
                if (this.robotList[i].type === RobotType.Scrap) {
                    count++;
                }
            }
            return count;
        }
    
        // 累計で倒した敵の数
        countTotalDeadEnemy(level: number): number {
            let total = 0;
            for (let l = level - 1; l > 0; l--) {
                total += l * 10;
            }
            return total + this.countDeadEnemy();
        }
    }
    
    

    Field 클래스(View)

  • 터미널에 필드, 안내서와 모든 로봇을 표시한다.
  • 로봇 등급이 관리하는 로봇의 배열을 참조하여 화면에 로봇을 표시한다.
  • 필드의 너비와 높이를readonly로 설정합니다.
  • field.ts
    import { RobotInfo, RobotType } from "./robot"
    
    // フィールドサイズをreadonlyで宣言
    export interface FieldSize {
        readonly width: number,
        readonly height: number,
    }
    
    // フィールド表示に必要な関数
    export interface FieldMethod {
        printField(): void;
        printGuide(level: number, score: number): void;
        printRobots(robotList: RobotInfo[]): void;
    }
    
    // 上記インタフェースを実装したFieldクラス
    export class FieldCUI implements FieldSize, FieldMethod {
        readonly width = 60;
        readonly height = 20;
    
        // tslint:disable-next-line: no-empty
        constructor() {
        }
    
        printField(): void {
            // tslint:disable-next-line: no-console
            console.clear()
            // top of field
            process.stdout.write("+")
            for (let i = 0; i < this.width; i++) {
                process.stdout.write("-")
            }
            process.stdout.write("+\n")
    
            // inside of field
            for (let j = 0; j < this.height; j++) {
                process.stdout.write("|")
                for (let i = 0; i < this.width; i++) {
                    process.stdout.write(" ")
                }
                process.stdout.write("|\n")
            }
    
            // bottom of field
            process.stdout.write("+")
            for (let i = 0; i < this.width; i++) {
                process.stdout.write("-")
            }
            process.stdout.write("+")
        }
    
        printGuide(level: number, score: number): void {
            // tslint:disable-next-line: variable-name
            const cursor_x = this.width + 3
            // tslint:disable-next-line: variable-name
            let cursor_y = 0
    
            process.stdout.cursorTo(cursor_x, cursor_y)
            process.stdout.write("\n")
            process.stdout.cursorTo(cursor_x, cursor_y++)
            cursor_y++
    
            process.stdout.write("Directions:\n")
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("y k u\n")
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write(" \\|/\n")
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("h- -l\n")
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write(" /|\\ \n")
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("b j n\n\n")
    
            cursor_y++
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("Commands:\n\n")
            cursor_y++
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("w: wait for end\n")
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("t: teleport\n")
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("q: quit\n\n")
    
            cursor_y++
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("Legend:\n\n")
            cursor_y++
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("+: robot\n")
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("*: junk heap\n")
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("@: you\n\n")
    
            cursor_y++
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("Level:" + level + "\n\n")
            process.stdout.cursorTo(cursor_x, cursor_y++)
            process.stdout.write("Score:" + score + "\n\n")
        }
    
        printRobots(robotList: RobotInfo[]) {
            for (const item of robotList) {
                process.stdout.cursorTo(item.x, item.y)
                if (item.type === RobotType.Player) {
                    // put player robot
                    process.stdout.write('@')
                } else if (item.type === RobotType.Enemy) {
                    // put enemy robots
                    process.stdout.write('+')
                } else if (item.type === RobotType.Scrap) {
                    // put scrap
                    process.stdout.write('*')
                } else {
                    ;
                }
            }
        }
    }
    
    

    게임 클래스(Ctroller에 해당)

  • 사용자의 버튼 입력을 받아 로보트, Field 클래스와 연합하여 게임 전체를 관리하는 클래스이다.
  • 버튼 입력용 npm 봉인(keypress)이 필요합니다.
  • game.ts
    import { FieldCUI } from './field';
    import { Robot, RobotMove } from './robot'
    
    export class Game {
        level: number;
        score: number;
    
        constructor() {
            this.level = 1;
            this.score = 0;
        }
        // Robots Start
        start(): void {
            const robot = new Robot();
            const fieldCUI = new FieldCUI();
            robot.makeRobots(fieldCUI.width, fieldCUI.height, this.level);
            fieldCUI.printField();
            fieldCUI.printRobots(robot.robotList);
            fieldCUI.printGuide(this.level, this.score);
    
            // keypressライブラリを読み込む
            const keypress = require('keypress');
            // keypressを標準入力に設定
            keypress(process.stdin);
            process.stdin.on('keypress', (ch: any, key: any) => {
                let toward: RobotMove = RobotMove.Wait;
                switch (ch) {
                    case 'y':
                        toward = RobotMove.UpperLeft;
                        break;
                    case 'k':
                        toward = RobotMove.Up;
                        break;
                    case 'u':
                        // 右上に1マス移動
                        toward = RobotMove.UpperRight;
                        break;
                    case 'l':
                        // 右に1マス移動
                        toward = RobotMove.Right;
                        break;
                    case 'n':
                        // 右下に1マス移動
                        toward = RobotMove.LowerRight;
                        break;
                    case 'j':
                        // 下に1マス移動
                        toward = RobotMove.Down;
                        break;
                    case 'b':
                        // 左下に1マス移動
                        toward = RobotMove.LowerLeft;
                        break;
                    case 'h':
                        // 左に1マス移動
                        toward = RobotMove.Left;
                        break;
                    case 'w':
                        // 待機
                        break;
                    case 't':
                        // スクラップ以外にテレポート. 運が悪いと敵の隣にテレポートで即死.
                        toward = RobotMove.Teleport;
                        break;
                    case 'q':
                        toward = RobotMove.Unknown;
                        process.stdin.pause();
                        break;
                    default:
                        toward = RobotMove.Wait;
                }
    
                if (robot.movePlayer(toward, fieldCUI.width, fieldCUI.height)) {
                    // ゲームオーバーのとき
                    if (!robot.moveEnemey()) {
                        process.stdout.write("Game Over...\n");
                        process.stdin.pause();
                    } else {
                        // 敵が全滅
                        if (robot.wipeOut()) {
                            robot.makeRobots(fieldCUI.width, fieldCUI.height, ++(this.level));
                        }
                        // ボーナス点を加味したスコア
                        let bonusSum = 0;
                        for (let level = this.level - 1; level > 0; level--) {
                            bonusSum += level * 100;
                        }
                        this.score = robot.countTotalDeadEnemy(this.level) * 10
                            + bonusSum;
                        // 画面表示
                        fieldCUI.printField();
                        fieldCUI.printRobots(robot.robotList);
                        fieldCUI.printGuide(this.level, this.score);
                    }
                }
            });
            // プロセス実行中のキー入力を拾うように設定
            process.stdin.setRawMode(true);
            process.stdin.resume();
        }
    }
    
    

    Primary(엔트리 포인트와 같음)

  • 게임 클래스를 호출합니다.
  • index.ts
    import { Game } from './game'
    
    const game = new Game();
    game.start();
    

    소스 코드


  • GitHub가 공개됐다.
  • 동작 확인

  • 팩스도 정상적으로 작동합니다.
    robots.gif
  • 끝말

  • 타입 스크립트와 친구가 된 것 같아요.
  • 저는 형 구동 개발이라고 해야 하나요, 아니면 Type Script의 장점이라고 해야 하나요?나는 재형선언에서 개발 효율과 안전성을 높이는 이유를 알았다.
  • 이번엔 GUI가 더 쉬워진 것 같아요.Robot 레벨은 변경하지 마십시오. View와 Controller는 프런트엔드에 대응할 수 있습니다.아마
  • 좋은 웹페이지 즐겨찾기