2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
पूर्वस्य डिजाइनस्य अनुसारं अस्माकं कृते क्रीडायाः आरम्भार्थं क्रीडायाः आवश्यकाः तत्त्वानि आवश्यकानि सन्ति ।
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();
पङ्क्तिपङ्क्तिविश्लेषणं कुर्मः : १.
संकुलस्य परिचयः tetris-core तथा tetris-console इति विभक्तः अस्ति एतत् संकुलस्य विभाजनम् अस्ति कोरसङ्कुलस्य घटकाः tetris-core इत्यत्र स्थापिताः सन्ति, विशिष्टं कार्यान्वयनञ्च tetris-console इत्यत्र स्थापितं भवति ।
विषयस्य, कैनवासस्य, नियन्त्रकस्य च आरम्भः;
कारखानस्य आरम्भः तथा आयामः;
क्रीडायाः आरम्भार्थं पूर्वं आरभितानां कैनवास, कारखाना, कैनवास, आयामवस्तूनाम् उपयोगं कुर्वन्तु;
क्रीडा आरम्भविधिं आह्वयति।
तदनन्तरं पश्यामः यत् आरम्भः किं करोति ?
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);
}
}
पङ्क्तिपङ्क्तिविश्लेषणं कुर्मः : १.
प्राप्नोतुstatus
चरः, २.status
कृतेGame
क्रीडावस्थायाः आन्तरिकप्रतिनिधित्वं क्रमशः准备就绪(READY)
、游戏中(RUNNING)
、暂停(PAUSE)
、停止(STOP)
、游戏结束(OVER)
. स्टॉप-क्रीडा-अन्तयोः भेदः अस्ति यत् पूर्वः सक्रियरूपेण क्रीडां स्थगयति, उत्तरं तु क्रीडायाः अन्त्य-तर्कं प्रेरयति, क्रीडायाः समाप्तिम् अपि जनयति
यदि क्रीडा प्रचलति तर्हि प्रत्यक्षतया प्रत्यागच्छन्तु;
यदि क्रीडा निवृत्ता क्रीडा समाप्तं च भवति तर्हिStage
एकं रीसेटं कुर्वन्तु तथा चcanvas
समग्रं पुनः आकर्षणं कुर्वन्तु।
यदि सज्जतायां क्रीडा निरन्तरं भवति तर्हि क्रीडा अधुना एव आरम्भीकरणं सम्पन्नवती, कदापि न आरब्धा इति अर्थः । इवेण्ट् बाइण्ड् कर्तुं प्रथमवारं च कैनवासं आकर्षयितुं नियन्त्रकं आह्वयन्तु;
क्रीडावस्थां इति सेट् कुर्वन्तु 游戏中(RUNNING)
, आन्तरिक अवस्था tickCount = 0;
स्थानांतरणcanvas
तत्क्षणमेव आंशिकं अद्यतनं कुर्वन्तु अत्र मुख्यं अद्यतनं यत् स्थितिः परिवर्तिता अस्ति, येन क्रीडायाः स्थितिः पुनः प्रस्तुतीकरणस्य आवश्यकता वर्तते;
समयनिर्धारकं आरभत, समयनिर्धारकसमयः this.speed, .speed
भविष्ये वयं तस्य क्रीडास्तरेन (अद्यापि समर्थितं न) सह मेलयितुम् विचारयिष्यामः ।
tickCount तन्त्रस्य प्रवर्तनस्य कारणं मुख्यतया कैनवासस्य अद्यतन-आवृत्तिः सुनिश्चित्य भवति ।
यथा उपरिष्टात् कोडात् दृश्यते, क्रीडायाः मूलतर्कः अस्तिstage.tick
, तस्य आन्तरिकं कार्यान्वयनम् अस्ति ।
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();
}
}
}
प्रथमं निर्धारयन्तु यत् क्रीडा समाप्तम् अस्ति वा स्वच्छताक्रिया क्रियते वा इति।
यदिcurrent
यदि रिक्तं भवति तर्हि प्रथमवारं क्रीडा लोड् कृत्वा पृथक् आरभ्यते इति अर्थः ।current
तथाnext
。
क्रीडा अन्त्यस्थितिं प्राप्नोति वा इति निर्धारयतु अर्थात्current
तथाpoints
तत्र आच्छादनम् । यदि आच्छादनं भवति तर्हि क्रीडा समाप्तम् इति चिह्निता भवति ।
वर्तमानधारा अधः गन्तुं शक्नोति वा इति निर्धारयतु यदि अधः गन्तुं शक्नोति तर्हि एकं स्थानं अधः गन्तुं शक्नोति वा इति ।
तदनन्तरं वयं पश्यामः यत् कथं निष्कासनं ज्ञातुं शक्यते अर्थात्handleClear
साक्षात्कारः ।
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);
}
}
}
यथा उपर्युक्तसङ्केतात् दृश्यते, सम्पूर्णा प्रक्रिया चतुर्धा विभक्ता अस्ति ।
वर्तमानं वर्तमानं च बिन्दून् समाविष्टं नूतनं pointsClone प्रतिलिपिं कुर्वन्तु ।
points ज्ञापयन्तुClone रेखां रेखा, तथा च चिह्नं कुर्वन्तु यत् यदि सम्पूर्णा रेखा पूरिता अस्ति;
2 इत्यस्मिन् उत्पन्नस्य चिह्नितसामग्रीनुसारं पङ्क्तिं पङ्क्तिं विलोपयन्तु। ध्यानं कुर्वन्तु यत् पङ्क्तिं विलोपनं कुर्वन् उपरितः अधः यावत् विलोपनक्रिया क्रियते ।
कार्यस्य स्वच्छता।एतत् पदं क्लियरिंग् ऑपरेशनं Assign pointsClone to इति न कृत्वा अपि कर्तव्यम्this.points
, एकस्मिन् समये सम्पूर्णम्current
तथाnext
नुदति।
खण्डानां परिभ्रमणं किं प्रचलति ?
सर्वे परिभ्रमणव्यवहाराः game.rotate पद्धतिं आह्वयित्वा प्रवर्तन्ते, यत्र नियन्त्रकेन परिभाषिताः घटनाः, बाह्य-आह्वानाः इत्यादयः सन्ति;
Game इत्यस्मिन् कार्यान्वितः तर्कः निम्नलिखितरूपेण अस्ति ।
class Game {
rotate() {
this.stage.rotate();
this.canvas.update();
}
}
अग्रे पश्यन्तुStage
साक्षात्कारः
class Stage {
rotate(): boolean {
if (!this.current) {
return false;
}
const canChange = this.current.canRotate(this.points);
if (canChange) {
this.current.rotate();
}
return false;
}
}
प्रथमं न्यायं कुरुतcurrent
अस्ति वा, यदि नास्ति तर्हि साक्षात् प्रत्यागमिष्यति;
स्थानांतरणcurrent
इत्यस्यcanRotate
वर्तमानस्थानं परिभ्रमितुं शक्यते वा इति परीक्षितुं विधिः यदि तत् चयनं कर्तुं शक्यते तर्हि परिभ्रमणविधिं परिभ्रमितुं आह्वयन्तु ।
अग्रे गत्वा पश्यामःBlock
इत्यस्यcanRotate
तथाrotate
प्रक्रिया।
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;
}
}
प्रथमं पश्यामःcanRotate
साक्षात्कारः ।
centerIndex प्राप्नुवन्तु, centerIndex इति परिभ्रमणस्य केन्द्रबिन्दुस्य अनुक्रमणिका अस्ति । प्रत्येकं चित्रं भिन्नं भवति, यथा IBlock, यत् निम्नलिखितरूपेण परिभाषितम् अस्ति ।
class IBlock extends Block {
getCenterIndex(): number {
return 1;
}
}
अर्थात् परिभ्रमणकेन्द्रबिन्दुः द्वितीयः नोडः ।इव口口口口
, द्वितीयः केन्द्रबिन्दुः口田口口
。
तदतिरिक्तं अस्य खण्डस्य डिजाइनं कुर्वन्तः अस्माभिः इदमपि विचारितम् यत् केचन खण्डाः परिभ्रमितुं न शक्यन्ते, यथा OBlock, येषां चयनं कर्तुं न शक्यते ।किन्तुgetCenterIndex
निर्वतनम्-1
。
परिवर्तनानां सरणीं प्राप्नुवन्तु सरणी वर्तमानघूर्णनस्य कोणः इति परिभाषिता अस्ति सरणीयाः सामग्रीः अन्तिमपरिभ्रमणस्य सापेक्षतया अस्य परिभ्रमणस्य कोणं प्रतिनिधियति ।इवIBlock
इति विवक्षितम् ।
class IBlock extends Block {
currentChangeIndex: number = -1;
getChanges(): number[] {
return [
Math.PI / 2,
0 - Math.PI / 2
];
}
}
अर्थात् प्रथमः परिभ्रमणं प्रारम्भिकस्थितिः Math.PI / 2 (अर्थात् 90 डिग्री), द्वितीयं च परिभ्रमणं प्रथमस्य परिभ्रमणस्य (अर्थात् -90 डिग्री) -Math.PI / 2 भवति यथा- १.
// 初始状态
// 口田口口
// 第一次旋转
// 口
// 田
// 口
// 口
// 第二次旋转
// 口田口口
पुनश्च: अत्र कृपया ज्ञातव्यं यत् निर्देशांक-अक्षः वामतः दक्षिणतः, उपरितः अधः च भवति ।
परिभ्रमणनिर्णयस्य कृते निर्णयमापदण्डाः सन्ति- १.
अतः सन्ति इति पश्यामःisValid
तथाnewPoints.every
न्यायः ।
अग्रे पश्यामःBlock.rotate
,यथा : १.
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;
}
}
उपर्युक्तवर्णनद्वारा, २.rotate
तर्कः सुलभः अस्ति ।
प्राप्नोतुcenterIndex
तथाchanges
,भविष्यतिcurrentChangeIndex
चक्रीयवृद्धिं कृत्वा Block इत्येतत् नूतननिर्देशाङ्कं प्रति सूचयन्तु ।
इत्यस्मिन्currentChangeIndex
प्रारम्भिकं मूल्यं -1 अस्ति, यस्य अर्थः अस्ति यत् वर्तमानं परिभ्रमणं प्रचलति, यदि च 0 इत्यस्मात् अधिकं वा समं वा भवति तर्हि अनुक्रमणिका + 1 चयनितम् इति अर्थः । (कृपया अत्र सम्यक् चिन्तयन्तु, यतः सरणीयाः अनुक्रमणिका 0 तः आरभ्यते)
गतिः इति अर्थः खण्डस्य चतुर्दिक्षु चालनम् ।तस्य कार्यान्वयनम् पश्यामः
class Game {
move(direction: Direction) {
this.stage.move(direction);
this.canvas.update();
}
}
तेषु दिग्विवक्षिता यथा ।
type Direction = 'up' | 'down' | 'left' | 'right';
अधिकं पश्यन्तुStage
कार्यान्वयनम् : १.
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;
}
}
अधिकं पश्यन्तुcanMove
तथाmove
साक्षात्कारः ।
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;
}
});
};
}
संक्षेपेण तस्य अनुवादः यथा भवति ।
उपरि गन्तुं सर्वे y-अक्षबिन्दवः 0 इत्यस्मात् अधिकाः (अर्थात् 1 इत्यस्मात् अधिकाः वा समानाः वा) भवेयुः, गमनात् परं बिन्दुः रिक्तबिन्दवः भवितुमर्हन्ति;
वामभागे शिफ्ट् कुर्वन्तु, सर्वे x-अक्षबिन्दवः 0 इत्यस्मात् अधिकाः भवेयुः (अर्थात् 1 इत्यस्मात् अधिकाः वा समानाः वा), तथा च चालनस्य अनन्तरं बिन्दवः रिक्तबिन्दवः भवेयुः;
दक्षिणदिशि स्थानान्तरणं कुर्वन्तु, सर्वे x-अक्षबिन्दवः x-निर्देशाङ्क-अक्षस्य -1 (अर्थात् xSize - 2 इत्यस्मात् न्यूनाः वा समानाः वा) दीर्घतायाः अपेक्षया न्यूनाः भवितुमर्हन्ति, तथा च चालनस्य अनन्तरं बिन्दवः रिक्तबिन्दवः भवेयुः
अधः गन्तुं सर्वे y-अक्षबिन्दवः y-समन्वय-अक्षस्य -1 (अर्थात् ySize - 2 इत्यस्मात् न्यूनाः वा समानाः वा) दीर्घतायाः अपेक्षया न्यूनाः भवितुमर्हन्ति, तथा च गमनात् परं बिन्दवः रिक्तबिन्दवः भवेयुः
गतिशर्ताः पूरयित्वा पश्यामःmove
साक्षात्कारः ।
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;
}
}
निर्देशांकबिन्दुस्य मूल्यं प्रत्यक्षतया परिवर्तयन्तु ।
अस्मिन् अध्याये क्रीडायाः त्रयः महत्त्वपूर्णाः व्यवहाराः वर्णिताः सन्ति - स्वच्छता, परिभ्रमणं, चलनं च । त्रयः परस्परं सहकार्यं कृत्वा क्रीडां सम्पन्नं कुर्वन्ति । अग्रिमे अध्याये वयं क्रीडायाः interface rendering तथा operation control इत्येतत् साझां कुर्मः ।