- एकल
World
वर्ग की जिम्मेदारी क्या है? यह केवल एक ब्लॉब है जिसमें व्यावहारिक रूप से हर तरह की कार्यक्षमता है। यह अच्छा डिजाइन नहीं है। एक स्पष्ट ज़िम्मेदारी "ग्रिड का प्रतिनिधित्व करती है जिस पर ब्लॉक लगाए जाते हैं"। लेकिन इसका टेट्रॉइड बनाने या ब्लॉक सूचियों या ड्राइंग में हेरफेर करने के साथ कुछ लेना देना नहीं है। असल में, उनमें से अधिकतर को कक्षा में होने की आवश्यकता नहीं है। मैं World
ऑब्जेक्ट को BlockList
रखने की अपेक्षा करता हूं, जिसे आप स्टेटिकब्लॉक्स कहते हैं ताकि यह उस ग्रिड को परिभाषित कर सके जिस पर आप खेल रहे हैं।
- आप अपना खुद का
Blocklist
क्यों परिभाषित करते हैं? आपने कहा था कि आप चाहते हैं कि आपका कोड सामान्य हो, तो किसी भी कंटेनर का उपयोग क्यों न करें? यदि मैं चाहूं तो std::vector<Block>
का उपयोग क्यों नहीं कर सकता? या std::set<Block>
, या कुछ घर-ब्रीड कंटेनर?
- उन साधारण नामों का उपयोग करें जो जानकारी डुप्लिकेट नहीं करते हैं या खुद को विरोधाभास नहीं करते हैं।
TranslateTetroid
एक tetroid का अनुवाद नहीं करता है। यह ब्लॉक ब्लॉक में सभी ब्लॉक का अनुवाद करता है। तो यह TranslateBlocks
या कुछ होना चाहिए। लेकिन यह भी अनावश्यक है। हम हस्ताक्षर से देख सकते हैं (यह BlockList&
लेता है) कि यह ब्लॉक पर काम करता है। तो बस इसे Translate
पर कॉल करें।
- सी-शैली टिप्पणियों से बचने का प्रयास करें (
/*...*/
)। सी ++ - शैली (//..
) उसमें थोड़ा सा अच्छा व्यवहार करता है यदि आप कोड के पूरे ब्लॉक को सी-स्टाइल टिप्पणी का उपयोग करते हैं, तो यह ब्लॉक तोड़ देगा यदि उस ब्लॉक में सी-स्टाइल टिप्पणियां भी शामिल हैं। (एक साधारण उदाहरण के रूप में, /*/**/*/
काम नहीं करेगा, क्योंकि संकलक टिप्पणी के अंत के रूप में पहले */
देखेंगे, और इसलिए अंतिम */
को कोई टिप्पणी नहीं माना जाएगा।
- सभी के साथ क्या है (अनामित)
int
मानकों? यह अपने कोड को पढ़ने के लिए असंभव बना रहा है।
- सम्मान भाषा सुविधाओं और सम्मेलनों। जिस तरह से एक वस्तु की नकल करने के लिए अपने प्रतिलिपि निर्माता उपयोग कर रहा है। तो बजाय एक
CopyTetroid
समारोह से, BlockList
एक प्रति निर्माता देते हैं। तो फिर अगर मैं करने की जरूरत है एक कॉपी करें, मैं बस BlockList b1 = b0
कर सकता हूं।
void SetX(Y)
और Y GetX()
विधियों की बजाय, अनावश्यक गेट/सेट प्री ठीक करें और बस void X(Y)
और Y X()
है। हम जानते हैं कि यह एक गेटर है क्योंकि इसमें कोई पैरामीटर नहीं होता है और एक मूल्य देता है। और हम जानते हैं कि दूसरा एक सेटटर है क्योंकि यह पैरामीटर लेता है और शून्य लौटाता है।
BlockList
एक बहुत अच्छा अमूर्त नहीं है। आपके पास "वर्तमान टेट्रॉइड" और "वर्तमान में ग्रिड पर स्थिर ब्लॉक की सूची" के लिए बहुत अलग आवश्यकताएं हैं। स्थिर ब्लॉक को आपके पास ब्लॉक के एक साधारण अनुक्रम द्वारा दर्शाया जा सकता है (हालांकि पंक्तियों का अनुक्रम, या 2 डी सरणी अधिक सुविधाजनक हो सकती है), लेकिन वर्तमान में सक्रिय टेट्रॉइड को अतिरिक्त जानकारी की आवश्यकता होती है, जैसे घूर्णन केंद्र (जो कि World
में संबंधित नहीं है)।
- टेट्रॉइड का प्रतिनिधित्व करने और घूर्णन को कम करने का एक आसान तरीका, सदस्य ब्लॉक घूर्णन के केंद्र से एक साधारण ऑफसेट स्टोर कर सकते हैं। इससे रोटेशन को गणना करना आसान हो जाता है, और इसका मतलब है कि सदस्य ब्लॉक को अनुवाद के दौरान अद्यतन नहीं किया जाना चाहिए। बस घूर्णन के केंद्र को स्थानांतरित किया जाना है।
- स्थिर सूची में, ब्लॉक के लिए उनके स्थान को जानने के लिए भी कुशल नहीं है।इसके बजाए, ग्रिड को ब्लॉक पर स्थानों को मानचित्र करना चाहिए (यदि मैं ग्रिड से पूछता हूं "कौन सा ब्लॉक सेल
(5,8)
में मौजूद है, तो यह ब्लॉक को वापस करने में सक्षम होना चाहिए। लेकिन ब्लॉक को समन्वय को स्टोर करने की आवश्यकता नहीं है। अगर ऐसा होता है, तो यह करता है रखरखाव सिरदर्द बन सकता है। क्या होगा यदि कुछ सूक्ष्म बग के कारण, दो ब्लॉक एक ही समन्वय के साथ समाप्त हो जाते हैं? ऐसा हो सकता है कि ब्लॉक अपने स्वयं के समन्वय को स्टोर करते हैं, लेकिन यदि ग्रिड में कौन सी ब्लॉक है, तो सूची नहीं है।)
- यह हमें बताता है कि हमें "स्थिर ब्लॉक" के लिए एक प्रतिनिधित्व की आवश्यकता है, और दूसरा "गतिशील ब्लॉक" के लिए (इसे ऑफसेट को टेट्रॉइड के केंद्र से स्टोर करने की आवश्यकता है)। वास्तव में, "स्थैतिक" ब्लॉक को उबाला जा सकता है अनिवार्यताओं के लिए: या तो ग्रिड में एक सेल में एक ब्लॉक होता है, और उस ब्लॉक में रंग होता है, या इसमें कोई ब्लॉक नहीं होता है। इन ब्लॉकों से कोई और व्यवहार नहीं होता है, इसलिए शायद वह कक्ष है जिसमें इसे रखा गया है वह श इसके बजाय मॉडलिंग किया जाना चाहिए।
- और हमें एक चलने योग्य/गतिशील टेट्रॉइड का प्रतिनिधित्व करने वाली कक्षा की आवश्यकता है।
- में है कि यह साथ "क्या हुआ अगर मैं यहाँ पर वस्तु ले जाया गया" सौदों के बाद से अपने टक्कर पता लगाने का एक बहुत "भविष्य कहनेवाला" है, यह गैर परिवर्तनशील अनुवाद/रोटेशन कार्यों को लागू करने में आसान हो सकता है। इन्हें मूल वस्तु को अनमोडिफाइड छोड़ देना चाहिए, और घुमावदार/अनुवादित प्रति वापस लौटा दी जानी चाहिए।
तो यहां आपके कोड पर पहला पास है, बस संरचना को बदलने के बिना कोड का नाम बदलने, टिप्पणी करने और हटाने के लिए। , हम एक ग्रिड में "डायनामिक" ब्लॉक, tetroid उन्हें मालिक, और स्थिर ब्लॉक या कोशिकाओं का प्रतिनिधित्व करने की आवश्यकता होगी
पहले:
class World
{
public:
// Constructor/Destructor
// the constructor should bring the object into a useful state.
// For that, it needs to know the dimensions of the grid it is creating, does it not?
World(int width, int height);
~World();
// none of thes have anything to do with the world
///* Blocks Operations */
//void AppendBlock(int, int, BlockList&);
//void RemoveBlock(Block*, BlockList&);;
// Tetroid Operations
// What's wrong with using BlockList's constructor for, well, constructing BlockLists? Why do you need NewTetroid?
//void NewTetroid(int, int, int, BlockList&);
// none of these belong in the World class. They deal with BlockLists, not the entire world.
//void TranslateTetroid(int, int, BlockList&);
//void RotateTetroid(int, BlockList&);
//void CopyTetroid(BlockList&, BlockList&);
// Drawing isn't the responsibility of the world
///* Draw */
//void DrawBlockList(BlockList&);
//void DrawWalls();
// these are generic functions used to test for collisions between any two blocklists. So don't place them in the grid/world class.
///* Collisions */
//bool TranslateCollide(int, int, BlockList&, BlockList&);
//bool RotateCollide(int, BlockList&, BlockList&);
//bool OverlapCollide(BlockList&, BlockList&); // For end of game
// given that these functions take the blocklist on which they're operating as an argument, why do they need to be members of this, or any, class?
// Game Mechanics
bool AnyCompleteLines(BlockList&); // Renamed. I assume that it returns true if *any* line is complete?
bool IsLineComplete(int line, BlockList&); // Renamed. Avoid ambiguous names like "CompleteLine". is that a command? (complete this line) or a question (is this line complete)?
void ColourLine(int line, BlockList&); // how is the line supposed to be coloured? Which colour?
void DestroyLine(int line, BlockList&);
void DropLine(int, BlockList&); // Drops all blocks above line
// bad terminology. The objects are rotated about the Z axis. The x/y coordinates around which it is rotated are not axes, just a point.
int rotationAxisX;
int rotationAxisY;
// what's this for? How many rotation states exist? what are they?
int rotationState; // Which rotation it is currently in
// same as above. What is this, what is it for?
int rotationModes; // How many diff rotations possible
private:
int wallX1;
int wallX2;
int wallY1;
int wallY2;
};
// The language already has perfectly well defined containers. No need to reinvent the wheel
//class BlockList
//{
//public:
// BlockList();
// ~BlockList();
//
// Block* GetFirst();
// Block* GetLast();
//
// /* List Operations */
// void Append(int, int);
// int Remove(Block*);
// int SearchY(int);
//
//private:
// Block *first;
// Block *last;
//};
struct Colour {
int r, g, b;
};
class Block
{
public:
Block(int x, int y);
~Block();
int X();
int Y();
void Colour(const Colour& col);
void Translate(int down, int left); // add parameter names so we know the direction in which it is being translated
// what were the three original parameters for? Surely we just need to know how many 90-degree rotations in a fixed direction (clockwise, for example) are desired?
void Rotate(int cwSteps);
// If rotate/translate is non-mutating and instead create new objects, we don't need these predictive collision functions.x ½
//// Return values simulating the operation (for collision purposes)
//int IfTranslateX(int);
//int IfTranslateY(int);
//int IfRotateX(int, int, int);
//int IfRotateY(int, int, int);
// the object shouldn't know how to draw itself. That's building an awful lot of complexity into the class
//void Draw();
//Block *next; // is there a next? How come? What does it mean? In which context?
private:
int x; // position x
int y; // position y
Colour col;
//int colourR;
//int colourG;
//int colourB;
};
// Because the argument block is passed by value it is implicitly copied, so we can modify that and return it
Block Translate(Block bl, int down, int left) {
return bl.Translate(down, left);
}
Block Rotate(Block bl, cwSteps) {
return bl.Rotate(cwSteps);
}
अब, चलो लापता टुकड़े में से कुछ जोड़ते हैं। (हम भी दुनिया/ग्रिड वर्ग के लिए एक सरल "टकरा" विधि जोड़ देंगे)
class Grid
{
public:
// Constructor/Destructor
Grid(int width, int height);
~Grid();
// perhaps these should be moved out into a separate "game mechanics" object
bool AnyCompleteLines();
bool IsLineComplete(int line);
void ColourLine(int line, Colour col);Which colour?
void DestroyLine(int line);
void DropLine(int);
int findFirstInColumn(int x, int y); // Starting from cell (x,y), find the first non-empty cell directly below it. This corresponds to the SearchY function in the old BlockList class
// To find the contents of cell (x,y) we can do cells[x + width*y]. Write a wrapper for this:
Cell& operator()(int x, int y) { return cells[x + width*y]; }
bool Collides(Tetroid& tet); // test if a tetroid collides with the blocks currently in the grid
private:
// we can compute the wall positions on demand from the grid dimensions
int leftWallX() { return 0; }
int rightWallX() { return width; }
int topWallY() { return 0; }
int bottomWallY { return height; }
int width;
int height;
// let this contain all the cells in the grid.
std::vector<Cell> cells;
};
// represents a cell in the game board grid
class Cell {
public:
bool hasBlock();
Colour Colour();
};
struct Colour {
int r, g, b;
};
class Block
{
public:
Block(int x, int y, Colour col);
~Block();
int X();
int Y();
void X(int);
void Y(int);
void Colour(const Colour& col);
private:
int x; // x-offset from center
int y; // y-offset from center
Colour col; // this could be moved to the Tetroid class, if you assume that tetroids are always single-coloured
};
class Tetroid { // since you want this generalized for more than just Tetris, perhaps this is a bad name
public:
template <typename BlockIter>
Tetroid(BlockIter first, BlockIter last); // given a range of blocks, as represented by an iterator pair, store the blocks in the tetroid
void Translate(int down, int left) {
centerX += left;
centerY += down;
}
void Rotate(int cwSteps) {
typedef std::vector<Block>::iterator iter;
for (iter cur = blocks.begin(); cur != blocks.end(); ++cur){
// rotate the block (*cur) cwSteps times 90 degrees clockwise.
// a naive (but inefficient, especially for large rotations) solution could be this:
// while there is clockwise rotation left to perform
for (; cwSteps > 0; --cwSteps){
int x = -cur->Y(); // assuming the Y axis points downwards, the new X offset is simply the old Y offset negated
int y = cur->X(); // and the new Y offset is the old X offset unmodified
cur->X(x);
cur->Y(y);
}
// if there is any counter-clockwise rotation to perform (if cwSteps was negative)
for (; cwSteps < 0; --cwSteps){
int x = cur->Y();
int y = -cur->X();
cur->X(x);
cur->Y(y);
}
}
}
private:
int centerX, centerY;
std::vector<Block> blocks;
};
Tetroid Translate(Tetroid tet, int down, int left) {
return tet.Translate(down, left);
}
Tetroid Rotate(Tetroid tet, cwSteps) {
return tet.Rotate(cwSteps);
}
और हम सट्टा टक्कर चेकों फिर से लागू करने की आवश्यकता होगी। यह देखते हुए गैर परिवर्तनशील अनुवाद/घुमाएँ तरीकों, कि सरल है: हम सिर्फ घुमाया/अनुवाद प्रतियां बनाते हैं, और टकराव के लिए उन का परीक्षण:
// test if a tetroid t would collide with the grid g if it was translated (x,y) units
if (g.Collides(Translate(t, x, y))) { ... }
// test if a tetroid t would collide with the grid g if it was rotated x times clockwise
if (g.Collides(Rotate(t, x))) { ... }
बिल्कुल अविश्वसनीय है, मैं वास्तव में क्या चाहते थे! आपका बहुत बहुत धन्यवाद! – Ash