This article outlines a course to demonstrate the development process of developing Tetris in Win32API. Suppose the student has studied C language, did not learn or learn C + + bad, just began to learn WIN32API program design, still do not understand the message loop and Register window class.
Recent photos are here [http://www.douban.com/photos/album/132796665/] and [http://www.douban.com/photos/album/133241544/].
1. Background and principles
I am speaking a course this semester, the third grade of undergraduate, the student is full 17 people. Generally close to full, the lowest one time 5 people, that day according to Lam Dong Doctrine, the other classmates went to see the football game.
The course name is called algorithm and program design Practice 3. The first lesson I have to explain as usual: to the "3" this stage, do not talk about algorithms, only practice. However, it is still a bit of an application to look at the algorithm, such as removing eligible elements from a linear table, and finding qualifying elements in the linear table, which is difficult.
Class is in the computer room, most of the time the students and teachers are looking at the display, so a semester down, many classmates and I meet may not know. But we are more familiar with the process of code formation.
I try to implement the following principles: Students should see the process of teacher programming, not just the results; Students should see the code in the editor and compiler, not in Word or PPT; Students should first learn the programming process of the pro-mode teacher without the ability to directly pro-mold results Students should even see the teacher's error and wrong resolution process, the teacher's ignorance and the retrieval process, students should not see the perfect programming process in advance and omniscient teachers, such processes and experts, students do not know how to imitate.
So, I'm not ready for class, and I'm not going to make any mistakes in class--occasionally demonstrating the mistakes students make--and solving them. Record our plans and current progress in the log file and draw prototypes in the drawing.
So, I pretended to be unfamiliar with some APIs and functions, demonstrating the steps to find manuals and solutions on MSDN and the Internet. Do some technical prototyping alone to verify the guesswork of the call results of the API, rather than testing the technology in the project code during the engineering process. Sometimes, I know where the problem is, but to list the possibilities first, and then to validate the conjecture (rather than directly, it seems to be a very easy mistake for a computer undergraduate, and if it solves it, it is the cause of the problem). In addition to these two points, I should be as honest as possible for the rest of the time.
Sometimes, the student union tells me what is wrong, before I find out the cause of the problem. This makes me enjoy this kind of teaching process.
In the end, we--with my code as the main--implemented the Tetris that Win32API developed.
The reason for choosing Tetris is that the business logic of the mini-game is complex enough to ensure that students understand the problems and programming behavior that they face when dealing with relatively complex business logic and are different from the toy works, using less technology, avoiding too many mechanisms (databases, networks, etc.) to distract students from their attention. Ensure that students focus on the business logic.
Choosing Win32API is the result of voting in class. There are two reasons to choose c without using C + +. The first is that the students ' C + + mastery is usually not proficient, and the second is that I hope that students can discover the necessity and merits of object-oriented in the project, instead of choosing the project only because of the language they have studied. Third, I want to demonstrate that C can also implement object-based programming (not object-oriented, not including inheritance, Includes only methods and data cohesion).
2. Technical prototypes
The technical prototypes involved, to establish a small project before the start of the project to verify the mastery of these techniques and the effect of conjecture.
The list of technologies to be tested comes from the requirements. We do not write code, describe the requirements verbally, and then decompose the requirements to the required technology. This creates a list of technologies. In this process, a definition is also formed, including the noun and verb tables.
These technical prototypes also define technologies that need to be mastered in addition to the C language, in this development.
Technical prototypes include:
* Use GDI to draw, erase. Used to draw small pieces and move small pieces. The movement is based on the visual persistence in the new position of the drawing, and the old position on the small pieces of the background re-painting.
* Keyboard message response. It is used to accept the player to move the small block left, right, rotate, fast drop without pausing the small block drop.
* Random number generation for a specific range. Used to decide which type to use when creating a new small block. Types include S-type, L-shaped, convex, field-shaped, and their rotations.
* Timer, used to drive the small block timing drop, determine whether to clear a row, scoring, refresh the workspace (redraw) and so on.
* Output text in the workspace. Used to debug and display fractions.
The resulting prototype part code volume is as follows. The code is in the prototype directory in the attachment
Drawing (and message loop), draw,226 line
Erase, eraser,263 Line
Output text in the workspace, textout,201 line
Key message response, key,207 line
Random number, random, 31 lines
Timer, timer,214 Line
3. Milestones in the development process
Once the technical prototype is determined, it is back to demand and the demand is scheduled. Try to complete a function each time the course is limited.
Requirements scheduling follows the principle that there is no reliance on other functions, and that the core functions are prioritized.
The following are milestones in the development process.
1) Generate blocks.
2) Timer drive, block auto drop
3) keyboard control block rotation, fast descent, left shift, right shift
4) fall to the end or stick to the existing block on the bottom (if (conficted | | touch_bottom) stick)
5) Delete one line: Delete one row and drop one line on top
6) Scoring: Eliminate the difference between a row and a multiline score
The following features are not implemented in this semester.
7) Display the next block in the preview area before creating a new block
8) The score accumulates to a certain extent (? ) to speed up the block drop
The development process records history in Git version control, one commit for each important function, and a date as a message.
4. Definition
We used a few definitions before development as a glossary. Typesetting reasons, I have a text here to explain.
Tetris element: The smallest unit of drawing on the workspace is a small square. Tetris's name Terris is four elements, because each current block consists of 4 elements.
Array element: An array element in the C language, one of the array. This definition is made to distinguish the elements of Tetris.
Current Block: A block of four elements being moved. There are types of s type, l type, Tian type and so on.
Existing block (exist block): An element that has been glued to a cluster at the bottom of the work area.
Pixel coordinates, world coordinates. The pixel coordinates are defined by the GDI drawing, and the world coordinates are defined by us, in element units, at the origin (0,0) on the left, and up to the right.
Stick The current block touches the existing block, or the current block touches the bottom of the workspace, the current block should be added to the existing block, and a new current block is generated, and if a row in the existing block is filled with elements, the row will need to be deleted by game rules, and then the element above this line in the existing block is dropped.
5. Data structure and process
The following describes the data structures and processes for the current block, existing blocks, keyboard operations, deleting a row in an existing block.
5.1 Current Block
The current block, including the following data for the current block: current coordinates, last coordinate (to erase), current type (explained next), last type (for rotation). The structure is as follows, and the entire program has only one instance of the struct.
struct struct_block{
int x;
int y; /* Row 0, col 0 */
int old_x;
int old_y;
int* type;
int* Old_type;
};
The type of the current block is implemented using an array, such as a font, field font, and convex font, respectively.
int line_v_block[]={0, 0, 0, 1, 0, 2, 0, 3};
int line_h_block[]={0,0,1,0,2,0,3,0};
int tian_block[]={0, 0, 0, 1, 1, 0, 1, 1};
int tu_v_block[]={0,1,1,0,1,1,2,1};
int tu_h_block[]={0,1,1,0,1,1,1,2};
Each of the two values in the array (the elements in the data) represents the coordinates of an element in the current block, and 8 values represent 4 elements.
When building blocks,
Current_block.type = Line_v_block;
The element that specifies the current block.
When drawing, iterate through the "type array" and draw each element out. Regardless of the type, follow this process to implement "take data as code": An array of types is the data, traversing an array of types, changing the type when rotated, and so on is the engine.
To rotate the code example, change the type (the pointer):
if (Current_block.type = = Line_v_block)
{
Current_block.type = Line_h_block;
}
To translate the code example, change the horizontal axis:
current_block.x-= 1;
A code example that automatically drops, changing the last ordinate and current ordinate.
if (! is_conflicted () &&! is_touch_bottom ())
{
Current_block.old_y = current_block.y;
CURRENT_BLOCK.Y = current_block.y + 1;
}
Else
{
Stick ();
Generate_block ();
}
Fast descent:
The ordinate increases the shortest distance from all elements that reach the bottom (or the top of the same horizontal axis in the block already in existence).
Seemingly digression, helper function: is_conflicted (), determine whether the current block is exposed to existing blocks, Is_touch_bottom (), determine whether the current block is touching the bottom, match the horizontal axis, give the current block of the bottom coordinate, the current block distance from the bottom of the minimum distance. Wait a minute.
The purpose of developing helper functions is to make the overall process of the program clear. One way to ensure a clear overall approach is to require that the content of each function not exceed one screen. If this is exceeded, the helper function needs to be broken out. Call the helper function in the main flow, and move the helper function body out of the main process so that the main process code length is reduced. This and the primary school writing text, the teacher asked to pull the outline is a truth. Often students say, in the development process will find new features, in the development of new technology, did not do prototype, it is difficult to grasp the outline. It all means that the ability to master an outline and plan is poor and needs to be trained through practice. This and the elementary school student writes writes the composition to find needs to look up the dictionary, or writes the digression, is a reason. We are not growing up with more words than we know, but we can foresee what words will be used (even the expression, writing material).
In addition, in object-oriented, some functions become member functions of the game (or current block or exist block). This in development recognizes how convenient it is to be able to get together with data in a class, so you understand the benefits of object-oriented information hiding. The class to which these functions belong belongs depends on which class assumes the responsibility.
5.2 Already existing block
The existing block includes the following data structure: the length of the block (in fact, the length of the block, the horizontal and vertical lines in the code as two array elements), the block array already exists. As follows.
int exist_block_size=0;
int exist_block[(maxx+1) * (maxy+1)];
This kind of data structure, and the current block of the database, the horizontal ordinate without difference, not in the structure of the way in the array, in the subsequent development of trouble. However, due to the limited duration of the course, I did not make any changes to this later. You should gradually evolve the program structure to form an array of elements as structures. Some helper and even member functions are developed, and are traversed in the Russian block element rather than in the current code in the array element unit.
One of the functions of an existing block data structure operation is stick, which moves the elements in the current block into an existing block when the current block touches the bottom (or touches an existing block).
There are a number of helper functions, basically by traversing the exist_block, according to the matching criteria to read the coordinates. Includes: match the horizontal axis, giving the top coordinate of the existing block int get_exist_block_top (int x).
5.3 Keyboard Actions & action sequences
The action of the player action block is initiated by the keyboard message response. Instead of dealing with this event in the keyboard response, we only remember this action here and join the action sequence. This is the later version. In the original version, we did not handle the event in the keyboard response, but instead called the function in Block.cpp. The principle is: where rely on Win32API, put in the tetris.cpp, such as timer, keyboard response, drawing; all related to business logic, platform-independent, placed in the block.cpp. Receive up ARROW, is the keyboard response, platform-related, so put in Tetris.cpp, this time called rotate, used to change the current block type or coordinates, platform-independent, so put in Block.cpp.
The data structure of the action sequence is as follows. In the action sequence array buffer_action_seq, the array action element
The type of (action) is the enumeration action.
Enum action{action_left=1, action_right=2, action_speed_down=3, action_rotate=4, action_down_auto=5, action_na=0};
Action Buffer_action_seq[action_size]={action_na};
int buffer_action_cursor = 0;
Starting with the player triggering the keyboard message, the process is as follows.
1) Keyboard message response:
buffer_action_seq[buffer_action_cursor++] = Action_rotate; Adds an action to the action sequence. This corresponds to the problem that the commander pattern in design mode solves.
2) automatic drop in timer
Timer in buffer_action_seq[buffer_action_cursor++] = Action_down_auto; Adds an action to the action sequence.
3) trigger WM_PAINT in Timer
InvalidateRect Trigger WM_PAINT in Timer
4) Execute action sequence in WM_PAINT
ERASE_OLD_BLOCK_SEQ (HDC);
ERASE_OLD_BLOCK_SEQ (HDC) iterates through the action sequence, changes the current block coordinates by each action, and then erases the old blocks resulting from the action. After traversing the sequence of actions, all the actions since the last timer cycle have been completed, erasing all the old blocks generated during this period.
The fragment of void Erase_old_block_seq (HDC hdc) is as follows:
for (i = 0; i < buffer_action_cursor; i++)
{
Switch (Buffer_action_seq[i])
{
Case Action_left:
Move_left ();
Erase_old_block (HDC);
Break
In each action in the sequence, move_left changes the coordinates, ERASE_OLD_BLOCK (HDC) erases the old block.
5) Wm_paint draw new current block and existing block
Draw_current_block (HDC);
Draw_exist_block (HDC);
Because repainting takes more time than the calculation, as a performance optimization, if the current block is exactly the same as the old block coordinates, it is not redrawn.
Another version of the action sequence, do not use enumerations and swtich-case, by the function as a message to the responsible person, the implementation of Disptach:
void (*next_action) () = Move_still;
Next_action = Move_left
Where Move_left is a function. Next_action such elements (types are functions) make up an array, as an action sequence. When executing an action sequence, use the following code:
while (next_action++! = action_termination)
Next_action;
Since next_action is both a function and a pointer to an array element, the above code is not pseudo-code, but can be executed. This is similar to the jump table technique, the type function of the array element, which can traverse the array and execute the function corresponding to the element.
5.4 Delete a row & Count score
In each timer, void Kill_all_full_lines () is called. It traverses the exist block, where a full-line condition is met, and the call Kill_block_in_line deletes the row, calling Move_exist_block_down_line to drop a row above the exist_block of the line.
Each of the three helper functions is done by traversing each element in the exist block, matching the coordinate conditions, and then deleting the array elements or changing the values of the array elements. As mentioned earlier, these loops are very ugly because the Tetris element is not used in the exist block package.
The cumulative number of deleted rows after a row is deleted. After total deletion, switch-case the number of rows deleted, and score the global variable with the accumulated score. In the next timer, export the score to the work area with TextOut.
6. Review and review
6.1 Data structures, packages, cyclic conditions
Since the initial (and final) data structure design stole the lazy, and later did not have enough time to revise, has already mentioned two times, exist block's structure is too close to the platform, but away from the demand. The granularity of the exist block is too low to be an array element of type int, corresponding to one of the horizontal ordinate in the Russian block element in the requirement. Whether an array element is a horizontal or vertical axis is the first of several Tetris elements that need to be implemented by code. In this way, when writing the helper function on demand, the traversal of the element selection, termination conditions, have encountered trouble. I need to think about it when I write in class, and sometimes it's wrong. Experience has it that when I need to think about it, or talk about it for a long time, it may be quite difficult for students to understand. The bug that terminated the condition error, there are two or three in the code, resulting in exist block enough, that is, the game for a period of time, the workspace will appear inexplicable Tetris elements. This bug was resolved at the last stage.
This story tells us that the design is not good, the code implementation of the difficulty requirements will be improved. Strategic mistakes, battles and battles are not easy to play. Leadership decision-making superficial, demanding subordinates run to death, the result is white pull. The truth is the same.
6.2 Don't deal with the past.
In the middle of the development of a class, we found that the current block moved in the back of the trail left behind, wiping is not clean. These classes are almost over. In order to allow students to repeat my work in class after class, I "dealt" with the code, changed from a local refresh to refresh the entire workspace, including the background. The surface of the trail was removed.
After that, the "deal" code continues. It was not until the end of the semester that I realized that this "deal" covered up other bugs, and that the movement of coordinates caused a trail to occur unless the entire workspace was refreshed. This bug was resolved at the last stage.
6.3 Parallel, Timer
It is pointed out that beginners are very difficult to understand the program concepts include: assignment, recursion and iteration, parallel. This procedure has a few deep buried bugs, because I do not have enough vigilance in parallel caused.
Timer, keyboard response, WM_PAINT will occur in parallel. When one of them is not finished, the other may start executing, and even the timer may start when it is not finished. And these parallel code, all call the Block.cpp. For example, sometimes causing one of the coordinates to be changed is not complete, the other starts to refresh the workspace, so there is an element in the workspace, the location is a mess.
Parallel processing requires atomic manipulation, interprocess communication, and avoidance of re-entry concepts. One of the purposes of the action sequence mentioned above is to erase the old current block, which occurs only in the timer.
In this course, students should not be expected to have knowledge of these operating systems. But I have not thought about how to design to avoid this knowledge. But I guess it should be similar to the design of a snake without threading, there should be a design method that relies on more superficial knowledge, such as simply polling instead of event response and message looping. Who knows, please enlighten me, thank you.
6.4 Conjecture, you should verify it before you modify it
The students usually take a step towards verifying the conjecture and implementing the settlement, and I often do so. Below them, including me.
They observed the problem and then made a guess. This is the normal step.
But they do not experiment to verify that the conjecture is correct, anxious to revise the code according to conjecture. If the problem disappears, well, they assume that the cause of the problem is caught, and if the problem is still there, make a guess and change it right away. Even worse, did not go back to the beginning of the previous step, in the current work code "continue" to modify, so that the various speculations add up, the final problem is not even know when the reason for the resolution.
You should design your experiment first, according to the conjecture model, and if so what happens. After verifying the conjecture, we will solve it. For example, if the timer and the keyboard event response synchronization causes the drawing confusion, then, should not be competing for the process of communication, but should first choose a simple rough method to remove the synchronization, with greater granularity as an atomic operation, to verify the conjecture. If the conjecture is correct, the phenomenon should change. Although it affects performance and effectiveness, it is not intended to be the final code to be used, but to validate the conjecture. Once the conjecture has been verified, think of a better solution, such as creating a variable as a semaphore.
6.5 Do not change the technical solution easily, try to bypass the problem
In this regard, I initially found that computer undergraduate students tend to be strong. Often have a plan, clearly another step forward can be solved, they are at this time changed the plan. Ask why. Answer: Because this technology can't solve this problem.
Determining "No" is extremely difficult, even more difficult than determining "can". You can't, not to be sure that this plan can't.
You need to be fully aware of the technology you use and have enough and clear confidence in the tasks it can accomplish. At the same time, it should be clear what problems can be solved by the solutions used to replace them. To do prototype verification, according to theoretical inference, these are the solutions. To see the tools, to use, piantingpianxin others ' comments, is too hasty, once found not the panacea, turn around to find the means, it is even more hasty.
6.6 Version Control
In order to allow students to see the development process, I use the file system in class to do version control, each class A directory, sometimes compressed into zip. After the course, a version of one version was added to Git and then commit, which operated for two hours (? In the meantime, worried about the whole mistake, miserable.
Be sure to do version control from the very beginning next time. You also need to manually delete debug, PCH, SDF and other binary garbage before commit.
7. Accessories
The attachment is a git version-controlled code and log, here [http://download.csdn.net/detail/younggift/7499881].
Protype is the technical prototype.
The Tetris is the Russian block project itself. The previous version was VS2010, and the last day was VS2012. You can add only the code section to the Win32 project to suit your vs version, or Dev C + + version.
Log0.txt is a diary in the classroom. Log1.txt is the last day of the previous log. Log2.doc is the last day of the late log, as needed, so change to Word.
Pic.bmp is a picture, used to illustrate the definition.
Branch is a branch, and I forget if it joins the trunk and stays there for backup, in case of omission.
--------------------
The blog will be manually synced to the following address:
[Http://giftdotyoung.blogspot.com]
[Http://blog.csdn.net/younggift]