코드스피츠 74 4회차

프로그램 짤때는 권한과 책임이 일치하게 짜야 된다. 권한과 책임이 일치하면 그것을 역활이라고 한다.

TETRIS 객체후보

  • STAGE(현재 스테이지 정보)
  • SCORE(점수 및 계산법)
  • BLOCK(범용 블록정의) - 색깔, 회전
  • 게임본체
  • 범용 패널
  • 시작화면
  • 스테이지 종료
  • 죽음
  • 클리어
  • 결과 화면

추상화

  • 일반화(카테고라이즈) - 기준점은 역활이다
  • 모델링 - 기억해야만 하는것
  • 그룹핑 - 랜덤하게 묶이는것(권한)

직접 통신 방법 보다 인터페이스를 통해서 통신해야 된다. 수정에 열려있다.


const Stage = class{
    init(listener){
        this.listener = listener;
    }
    clear(){
        this.stage = 0;
        this.next();
    }
    next(){
        if(this.stage++ < Stage.maxStage){
            this.speed = 500 - 450 * this.stage / Stage.maxStage;
            this.listener();
        }
    }
    [Symbol.toPrimitive](hint){
        return `<div>Stage ${this.stage}</div>`;
    }
};

Stage.maxStage = 20;

const Score = class{
        init(listener){
            this.listener = listener;
        }
        clear(){
            this.curr = this.total = 0;
        }
        add(line, stage){
            const score = parseInt((stage * 5) * (2 * line));
            this.curr += score, this.total += score;
            this.listener();
        }
        [Symbol.toPrimitive](hint){
            return `<div>Stage ${this.curr} / ${this.total}</div>`;
        }
};

블록 부모 클래스 정의


const Block  = class{
    constructor(color){
        Object.assign(this,{color, rotate:0});
    }
    left(){
        if(--this.rotate <0){
            this.rotate = 3;
        }
    }
    right(){
        if(++this.rotate <3){
            this.rotate = 0;
        }
    }
    getBlock(){
        throw 'override!';
    }
};

블록 자식클래스 정의


const blocks = [class extends Block, ....];

class extends Block{
    constructor(){
        super('#f8cbad');
    }
    getBlock(){
        return this.rotate % 2 ? [[1],[1],[1],[1]] : [[1,1,1,1]] 
    }

};


class extends Block{
    constructor(){
        super('#ffe699');
    }
    getBlock(){
        switch (this.rotate){
            case 0:return [[0,1,0],[1,1,1]];
            case 1:return [[1,0],[1,1],[1,0]];
            case 2:return [[1,1,1],[0,1,0]];
            case 3:return [[0,1],[1,1],[0,1]];
        }
    }

};

위에서 자식 클래스에서 중복이 생김 부모 클래스에 추가작업


const Block  = class{
    constructor(color){
        Object.assign(this,{color, rotate:0});
    }
    left(){
        if(--this.rotate <0){
            this.rotate = 3;
        }
    }
    right(){
        if(++this.rotate <3){
            this.rotate = 0;
        }
    }
    getBlock(){
        return this.blocks[this.rotate];
    }
};

랜더러 생성


const Renderer = class{
    constructor(col, row, base, back){
        Object.assign(this,{col, row, base, back, blocks:[]});
    }
    
    clear(){
        throw 'override!';
    }
    
    render(data){
        if(!(data instanceof Data)) throw 'invalid data';
        this._render(data);
    }
    _render(data){
        throw 'override!';
    }
};

데이터 생성


const Data = class extends Array{
    constructor(col, row,){
        Object.assign(this,{col, row});
    }
    
    cell(row,col,color){
        if(row > this.row || col > this.col) throw 'invalid';
        (this[row] || (this[row] = []))[col] = color;
    }
    
    row(row, ...color){
        color.forEach((v, i)=> this.cell(row,i,v));
    }
    
    all(...rows){
        rows.forEach((v, i)=> this.row(i, ...v));
    }
};

랜더러 상속후 구현


const el = el=>document.createElement(el);
const back = (s, v){
    s.backgroundColor = v;
};

const TableRenderer = class extends Renderer{
    constructor(col, row, base, back, style){
        super(col, row, el('table'), back)
        const {col, base, blocks} = this;
        base.style.cssText = style;
        let i = this.row;
        while (i--){
            const tr = base.appendChild(el('tr'));
            const curr = [];
            let j = col;
            blocks.push(curr);
            while (j--) curr.push(tr.appendChild(el('td')).style);
        }
    }
    clear(){
        this.blocks.forEach(curr=>curr.forEach(s=>back(s,this.back)));
    }
    _render(v){
        this.blocks.forEach((curr,i)=>curr.forEach((s,j)=>back(s, v[i][j])));
    }
    
};

캔버스


const el = el=>document.createElement(el);
const back = (s, v){
    s.backgroundColor = v;
};

const CanvasRenderer = class extends Renderer{
    constructor(col, row, back, style){
        super(col, row, el('canvas'))
        const {col, base, blocks} = this;
        base.style.cssText = style;
        Object.assign(this, {
            width:base.width = parseInt(base.style.width),
            height:base.height = parseInt(base.style.height),
            cellSize:[base.width/col,base.height/row],
            ctx:base.getContext('2D')
        });
    }
    clear(){
        this.ctx.clearRect(0,0,this.width, this.height);
    }
    _render(v){
      this.clear();
      const {col, ctx, cellSize:[w,h]} = this;
      let {row:i} = this;
      while (i--){
          let j = col;
          while (j--){
              ctx.fillStyle = v[i][j];
              ctx.fillRect(j*w,I*h,w,h);
          }
      }
    }
    
};

참조