Technology sharing

Ludens Tetris per manus (3) - core moduli designa lusionis

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Ludens Tetris per manus - core moduli designa ludi

Satus ludum

Secundum consilium priorem, elementa ludi necessarii opus est ut ludus committitur. Exemplum Tetris in console explicandi cursus Sit.

import { ConsoleCanvas, ConsoleController, ConsoleColorTheme, Color } from '@shushanfx/tetris-console';
import { Dimension, ColorFactory, Game } from '@shushanfx/tetris-core';

const theme = new ConsoleColorTheme();
const canvas = new ConsoleCanvas(theme);
const controller = new ConsoleController();
const dimension = new Dimension(10, 20);
const factory = new ColorFactory(dimension, [
  Color.red,
  Color.green,
  Color.yellow,
  Color.blue,
  Color.magenta,
  Color.cyan,
]);
const game = new Game({ dimension, canvas, factory, controller });
game.start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Analyze eam lineam in linea:

  • Introductio fasciculorum dividitur in nucleum tetris et consolatorium. Haec est divisio fasciculorum.

  • Initializationis thematis, canvas et moderatoris;

  • Initialization officinarum et dimensionum;

  • Ad lusum initializationem, uti carbasa initialized antea, officinas, canvas, et objecta dimensio;

  • Ludum vocat modum initium.

Deinde inspice quid facit initium?

Game.start logica


class Game {
  start() {
    const { status } = this;
    if (status === GameStatus.RUNNING) {
      return ;
    }
    if (status === GameStatus.OVER
      || status === GameStatus.STOP) {
      this.stage.reset();
      this.canvas.render();
    } else if (status === GameStatus.READY) {
      this.controller?.bind();
      this.canvas.render();
    }
    this.status = GameStatus.RUNNING;
    this.tickCount = 0;
    this.canvas.update();
    // @ts-ignore
    this.tickTimer = setInterval(() => {
      if (this.tickCount == 0) {
        // 处理向下
        this.stage.tick();
        this.checkIsOver();
      }
      this.canvas.update();
      this.tickCount++;
      if (this.tickCount >= this.tickMaxCount) {
        this.tickCount = 0;
      }
    }, this.speed);
  }
}

  • 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
  • 30
  • 31
  • 32
  • 33
  • 34

Expande eam lineam per lineam:

  1. obtinestatusvariabilis;statusfor*GameInterna repraesentatio ludi status, respective准备就绪(READY)游戏中(RUNNING)暂停(PAUSE)停止(STOP)游戏结束(OVER) . Discrimen finis inter finem et ludum est quod ille sistit activum ludum, hic vero finem logicae ludi facit et finem facit ludum.

  2. Si ludus in cursu est, directe redi;

  3. Si ludus sistitur et ludus est, tuncStagepraestare et resetcanvasPraestare altiore redraw.

  4. Si ludus perseveret dum parat, ludus significat initializationem modo completam et numquam incepit. Gubernatorem voca ad res ligandas et primum carbasa trahere;

  5. Constitue ludo civitatis to 游戏中(RUNNING), status internus tickCount = 0;

  6. translatiocanvasPraecipua renovatio partialis illico praestanda est. Hic principalis renovatio est quod status mutatus est, ut status ludi causando opus restituatur;

  7. In mauris metus, tempus faucibus cursus.speedIn futurum, eam congruentem cum gradu ludi (non sustinetur adhuc).

  • Si tickCount == 0, trigger actionem ricinum scaenae, et deprime an statim post excitato desinat;
  • Felis ad update operandi pariete
  • tickCount sponte crescit et reset est si >= tickMaxCount satiata est;

Causa quare tickCount mechanismum introducitur, maxime est ad frequentiam carbasi curandam.

Stage tick

Core logica ludi, ut ex superiori codice videri poteststage.tickinterna exsecutio talis est;

class Stage {
  tick(): void {
    if (this.isOver || this.clearTimers.length > 0) {
      return;
    }
    // 首次加载,current为空
    if (!this.current) {
      this.next = this.factory.randomBlock();
      this.toTop(this.next);
      this.current = this.factory.randomBlock();
      this.toTop(this.current);
      return ;
    }
    const isOver = this.current.points.some((point) => {
      return !this.points[point.y][point.x].isEmpty;
    });
    if (isOver) {
      this.isOver = true;
      return;
    }
    const canMove = this.current.canMove('down', this.points);
    if (canMove) {
      this.current.move('down');
    } else {
      this.handleClear();
    }
  }
}
  • 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
  • Primum determinare utrum ludus praesit vel operatio tersus exercetur.

  • sicurrentSi inanis, ludus est oneratus primum et separatim initialized.currentetnext

  • Utrum ludus conditionis finem attingat, id estcurrentetpoints Aliudque est. Si aliudque est, ludus ut supra designatus est.

  • Decernite an hic currens possit deorsum movere.

Deinde videamus quomodo eliminationem deprehendere, id esthandleCleareffectio.

class Stage {
  private handleClear() {
    if (!this.current) {
      return;
    }
    // 1. 复制新的points
    const pointsClone: Point[][] = this.points.map((row) => row.map((point) => point.clone()));
    this.current.points.forEach((point) => {
      pointsClone[point.y][point.x] = point.clone();
    });
    // 2. 检查是否有消除的行
    const cleanRows: number[] = [];
    for(let i = 0; i < pointsClone.length; i ++) {
      const row = pointsClone[i];
      const isFull = row.every((point) => {
        return !point.isEmpty
      });
      if (isFull) {
        cleanRows.push(i);
      }
    }
    // 3. 对行进行消除
    if (cleanRows.length > 0) {
      this.startClear(pointsClone, cleanRows, () => {
        // 处理计算分数
        this.score += this.getScore(cleanRows.length);
        // 处理消除和下落
        cleanRows.forEach((rowIndex) => {
          for(let i = rowIndex; i >= 0; i--) {
            if (i === 0) {
              pointsClone[0] = Array.from({ length: this.dimension.xSize }, () => new Point(-1, -1));
            } else {
              pointsClone[i] = pointsClone[i - 1];
            }
          }
        });
        // 4. 扫尾工作,变量赋值
        this.points = pointsClone;
        this.current = this.next;
        this.next = this.factory.randomBlock();
        this.toTop(this.next);
      });
    } else {
      // 4. 扫尾工作,变量赋值
      this.points = pointsClone;
      this.current = this.next;
      this.next = this.factory.randomBlock();
      this.toTop(this.next);
    }
  }
}
  • 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
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

Totus processus in quatuor gradus, sicut ex praedicto codice videri potest, dividitur.

  1. Effingo nova puncta Clone, inclusa currenti et currenti puncta.

  2. Deprehende punctaClone lineam per lineam, et nota, si tota linea impleatur;

  3. Linea dele per lineam secundum contentum notatum in II generatum. Nota quod operatio deletionis a summo ad imum perficitur.

  4. Purgato opere.Hic gradus opus est ad faciendam sive defensionem operationis faciendamthis.pointsPerfice simulcurrentetnexttransibit.

rotate

Quid agitur cum rotatione cuneos?

Omnes gyrationis mores in ludo.rotate methodo vocando utitur, etiam eventus a moderatore definito, vocato externo, etc.;

Logica in Ludo effecta haec est:

class Game {
  rotate() {
    this.stage.rotate();  
    this.canvas.update();
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Vigilate alteraStageEffectio

class Stage {
  rotate(): boolean {
    if (!this.current) {
      return false;
    }
    const canChange = this.current.canRotate(this.points);
    if (canChange) {
      this.current.rotate();
    }
    return false;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • judicas primumcurrentSive sit, si non est, directe reddetur;

  • translatiocurrentof*canRotateModus inspiciendi utrum positio hodierna revolvi possit;

Eamus longius et videBlockof*canRotateetrotatemodum.

class Block {
  canRotate(points: Point[][]): boolean {
    const centerIndex = this.getCenterIndex();
    if (centerIndex === -1) {
      return false;
    }
    const changes = this.getChanges();
    if (changes.length === 0) {
      return false;
    }
    const nextChange = changes[(this.currentChangeIndex + 1) % changes.length];
    const newPoints = this.changePoints(this.points, this.points[centerIndex], nextChange);
    const isValid = Block.isValid(newPoints, this.dimension);
    if (isValid) {
      return newPoints.every((point) => {
        return points[point.y][point.x].isEmpty;
      });
    }
    return isValid;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Inspice primumcanRotateeffectio.

  • Get centerIndex, centerIndex est index centri punctum rotationis. Singulae graphicae diversae sunt, ut IBlock, quae sic definitur:

    class IBlock extends Block {
      getCenterIndex(): number {
        return 1;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Id est, centrum gyrationis punctum est secundus nodi.sicut口口口口, punctum centrum secundi口田口口

    Praeterea cum hunc scandalum cogitamus, etiam duxi impedimenta aliqua rotari non posse, ut OBlock, quod seligi non potest.sedgetCenterIndexreditus-1

  • Ordinationes mutationes obtine. Ordinatio definitur angulus rotationis currentis. Longitudo ordinata numerus rotarum repraesentat.sicutIBlockdefinitur sic:

    class IBlock extends Block {
      currentChangeIndex: number = -1;
      getChanges(): number[] {
        return [
          Math.PI / 2,
          0 - Math.PI / 2
        ];
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Id est, prima rotatio est status initialis Math.PI/2 (id est 90 gradus), et secunda rotatio -Math.PI/2 primae rotationis (id est -90 gradus). sic:

    // 初始状态
    // 口田口口
    
    // 第一次旋转
    //  口
    //  田
    //  口
    //  口
    
    // 第二次旋转
    // 口田口口
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    PS: Quaeso hic nota quod axis coordinatus est a sinistro ad dextram et ad imum.

  • Iudicium enim rotationis, re- gulae iudicium sunt;

    1. Puncta applicata rotata fines totius ludi excedere non possunt;
    2. Puncta applicata rotata puncta quadrata impleta occupare non possunt.

    Videmus ergo quod suntisValidetnewPoints.everyjudicium.

Deinde videamusBlock.rotatesic:

class Block {
  rotate() {
    const centerIndex = this.getCenterIndex();
    if (centerIndex === -1) {
      return false;
    }
    const changes = this.getChanges();
    if (changes.length === 0) {
      return false;
    }
    const nextChange = changes[(this.currentChangeIndex + 1) % changes.length];
    const newPoints = this.changePoints(this.points, this.points[centerIndex], nextChange);
    const isValid = Block.isValid(newPoints, this.dimension);
    if (isValid) {
      this.currentChangeIndex = (this.currentChangeIndex + 1) % changes.length;
      this.points = newPoints;
    }
    return isValid;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

Per descriptionem, etc.rotateRatiocinatio facilis est ad intelligendum.

  • obtinecenterIndexetchanges, VoluntascurrentChangeIndexIncrementum cyclicum praestare et impedimentum coordinatis novis monstrare.

  • incurrentChangeIndex Valor initialis est -1, quod significat rotationem currentem in progressu, et si maior quam vel 0 aequalis est, significat index + 1 selegisse. (Quaeso hic diligenter cogita, quia index ordinatae ab 0 incipit)

move

Motus significat movere Clausum in quattuor partes.Videamus eius exsecutionem

class Game {
  move(direction: Direction) {
    this.stage.move(direction);
    this.canvas.update();
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Inter quos sic definitur Ditio:

type Direction = 'up' | 'down' | 'left' | 'right'
  • 1

Vide ampliusStageExsecutio:

class Stage {
  move(direction: Direction) {
    if (!this.current) {
      return false;
    }
    const canMove = this.current.canMove(direction, this.points);
    if (canMove) {
      this.current.move(direction);
    }
    return canMove;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Vide ampliuscanMoveetmoveeffectio.

class Block {
  canMove(direction: Direction, points: Point[][]): boolean {
    return this.points.every((point) => {
      switch (direction) {
        case 'up':
          return point.y > 0 && points[point.y - 1][point.x].isEmpty;
        case 'down':
          return point.y < this.dimension.ySize - 1 && points[point.y + 1][point.x].isEmpty;
        case 'left':
          return point.x > 0 && points[point.y][point.x - 1].isEmpty;
        case 'right':
          return point.x < this.dimension.xSize - 1 && points[point.y][point.x + 1].isEmpty;
      }
    });
  };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Breviter hoc modo vertamus:

  • Ad sursum movendum omnia puncta y-axa majora esse debent quam 0 (id est, majora vel aequalia 1), et puncta post motum vacua esse debent puncta;

  • Sinistra diverticula, puncta omnia x-axa majora esse debent quam 0 (id est, majora vel aequalia 1), punctaque post motum vacua puncta esse debent;

  • Trabea ad dextram, omnia puncta x-axis minora esse debent quam longitudo axis x-coordinate -1 (hoc est, minor quam vel aequalis xSize - 2), et puncta post motum debent esse puncta vacua;

  • Ad movendum omnia puncta y-axis minora esse debent quam longitudo axis -1 (hoc est, minor quam vel aequalis ySize - 2), et puncta post motum debent esse puncta vacua.

Postquam condiciones motus occurrentes, inspiciamusmoveeffectio.

class Block {
  move(direction: Direction): boolean {
    switch (direction) {
      case 'up':
        this.points.forEach((point) => { point.y = point.y - 1})
        break;
      case 'down':
        this.points.forEach((point) => { point.y = point.y + 1})
        break;
      case 'left':
        this.points.forEach((point) => { point.x = point.x - 1})
        break;
      case 'right':
        this.points.forEach((point) => { point.x = point.x + 1})
        break;
    }
    return true;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

Directe modificare valorem puncti coordinati.

summarium

In hoc capite tres magni mores ludi describit: defensionem, gyrationem, et motum. Tres eorum ad ludum complendum inter se cooperantur. In capite sequenti erimus instrumenti reddendi et operandi potestatem ludi.