This article outlines a course to demonstrate the development process of developing Tetris in Win32API. If students learn C language, did not learn or learn C + + is not good, just started to learn Win32API program design, but also do not know the message loop and register form 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: "3" At this stage, do not talk about the algorithm, only practice. It's just that the algorithm is still a little bit used, for example, to remove the eligible elements from a linear table, to find the elements that match the conditions in the linear table, it's 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. It's just that we're 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 am not prepared before class, in the classroom no intention to make all kinds of mistakes--occasionally demonstrating the students easy to make mistakes-and solve. Record our plans and current progress in the log file and draw prototypes in the drawing.
So, I pretended to be unfamiliar with some of the APIs and functions, demonstrating the steps to find manuals and workarounds in MSDN and the Internet. Do some technical prototypes alone to verify the guesswork of the call results of the API, rather than testing the technology in project code in the program's process. Sometimes, I know where the problem is, but to first list the various possibilities, and then validate the conjecture (rather than directly solve it, it seems to be a very easy mistake for computer undergraduates, assuming that it is a solution to 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 a solution to the problem. This makes me enjoy the teaching process.
Finally, 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 are aware of the problems and programming behavior that they face when dealing with relatively complex business logic and that the toy works are different from those used to less technology and avoid excessive mechanisms (databases, networks, etc.) distracting students 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 merit of object-oriented in the project, instead of choosing it in 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, without inheritance, Contains only methods and data cohesion).
2. Technical prototypes
The technical prototypes involved, to establish a small project before project starts to validate the mastery of these techniques and the guesswork of the results.
The list of technologies to be tested comes from the requirements. We do not write code, verbally describe the requirements, and then decompose the requirements to the required technology. This creates a list of technologies. In this process, a definition is also formed at the same time, including the noun and verb tables.
These technical prototypes also define the technologies that need to be mastered in addition to the C language, which was developed in this case.
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, high-speed 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 a small block timed drop, infer whether to clear a row, score, refresh the workspace (redraw), etc.
* Output text in the workspace. Used to debug and display fractions.
Finally form the prototype part of the code volume for example below. The code is under the prototype folder 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 prototypes are identified, they return to demand and schedule the demand. Try to qualify a function for each course.
Requirement scheduling follows the principle that there is no reliance on other functions, and the core function is completed first.
The following are milestones in the development process.
1) Generate blocks.
2) Timer drive, block own active descent
3) keyboard control block rotation, high-speed descent, left shift, right shift
4) fall or stick on the bottom already existing block (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 number 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 paint on the work area is a small square. Tetris's name Terris is four elements, since each current block consists of 4 elements.
Array element: An array element in the C language, one of the array. This definition is proposed to be different from 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. Pixel coordinates are defined by GDI paint, and the world coordinates are defined by us, in element units, on the left, at the origin (0,0), and down to the right.
Stick The current block touches a block that already exists, 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, assuming that a row in the existing block is filled with elements, it is necessary to delete the row by game rules, and then drop the element above this line in the existing block.
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 contains the following data for the current block: current coordinates, last coordinate (to erase), current type (explained next), last type (for rotation). Structs, such as the following, have only one instance of the struct in the entire program.
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 the following, each of which is a typeface, a field font, and a convex font.
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" to draw each element. Regardless of the type, follow this process to implement "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.
The rotated code demonstrates the sample, changing the type (the pointer):
if (Current_block.type = = Line_v_block)
{
Current_block.type = Line_h_block;
}
Translate the code to demonstrate the sample and change the horizontal axis:
current_block.x-= 1;
Self-actively dropped code demo sample, change 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 ();
}
High-speed descent:
The ordinate adds the shortest distance from all elements that reach the bottom (or the top of the same horizontal axis that already exists in the block).
Seemingly off-topic, helper Function: is_conflicted (), infer whether the current block is touching the existing block, Is_touch_bottom (), infer whether the current block touches the bottom, match the horizontal axis, give the bottom coordinate of the current block, and find the shortest distance from the bottom of the current block. Wait a minute.
The purpose of developing helper functions is to make the overall process of the program clear. One of the ways to protect the overall clarity is to require that each function content must not exceed one screen. If the hypothesis 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 some students say that in the development process will find new features, in the development of new technology, no prototype, it is difficult to grasp the outline. It all means that the ability to master and plan is poor, and it needs to be trained through practice. This and the elementary school student writes writes the composition discovery to need to look up the dictionary, or writes the topic, is a reason. We grow up not to know more words, but to foresee what words will be used (even expressions, writing materials).
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 gather in a class with data to 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 contains the following data structure: the length of the block (in fact, the length of the block, in the code with the horizontal and vertical coordinates as two array elements), there is already a block array. For example, the following.
int exist_block_size=0;
int exist_block[(maxx+1) * (maxy+1)];
This data structure, and the current block of data structure, the horizontal ordinate without distinction, not in the structure of the way to put in the array, in the development of perhaps the trouble. Just because the course time was limited, I didn't make any changes to it 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 that already exist in the block data structure operation is stick, which moves the elements in the current block into the existing block when the current block touches the bottom (or touches the existing block).
There are a number of helper functions, basically by traversing the exist_block, according to the matching criteria to read the coordinates. Contains: 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 started by the keyboard message response. Instead of dealing with this event in the keyboard response, we just remember this action here and add the action sequence. This is the later version number. The initial version number, and we do not handle the event in the keyboard response, but instead call the function in Block.cpp. The principle is: Anyone who relies on Win32API, put in 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;
The process starts with the player triggering a keyboard message, such as the following.
1) Keyboard message response:
buffer_action_seq[buffer_action_cursor++] = Action_rotate; Adds an action to the action sequence. This corresponds to the problem of the commander pattern in design mode to be solved.
2) Drop yourself in the 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) operation sequence in WM_PAINT
ERASE_OLD_BLOCK_SEQ (HDC);
ERASE_OLD_BLOCK_SEQ (HDC) iterates through the sequence of actions, changes the current block coordinates by each action, and then erases the old blocks generated by the action. After traversing the sequence of actions, the entire action has been completed since the last timer cycle, erasing all the old blocks produced during this period.
A fragment of void Erase_old_block_seq (HDC hdc) such as the following:
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 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);
Since repainting takes more time than computation, as a performance optimization, it is assumed that the current block is exactly the same as the old block coordinates and does not repaint.
In addition, there is a version number 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
The Move_left is a function. Next_action this element (type is a function) makes up an array, as an action sequence. When you run the action sequence, use the following code:
while (next_action++! = action_termination)
Next_action;
Because Next_action is both a function and a pointer to an array element, the above code is not pseudo-code, but is capable of running. This is similar to the jump table technique, the type function of an array element, the ability to iterate over an array, and to run the corresponding function of the element.
5.4 Delete a row & Count score
In each timer, a 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, because the Tetris element is not used in the exist block package, these loops are written ugly.
The cumulative number of deleted rows after a row is deleted. After the total deletion, the number of rows deleted is switch-case, and the global variable score the accumulated fraction. In the next timer, export the score to the work area with TextOut.
6. Recall and review
6.1 Data structures, packages, cyclic conditions
Because the original (and finally) data structure design stole lazy, and then did not have enough time to change, had already mentioned two times, exist block structure too close to the platform, and 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 have to think about it when I write in class, and sometimes it's wrong. Experience shows that when I need to think about it carefully, or when it's 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 fight. 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, so I "deal with" the code, the 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 some bugs, and the bug that the coordinates were moving resulted in a trail unless the entire workspace was refreshed. This bug was resolved at the last stage.
6.3 Parallel, Timer
It is pointed out that the people who have just started learning are not easy to understand the program concepts include: assignment, recursion and iteration, parallel. There are a few deep buried bugs in this program because I don't have enough vigilance for parallelism.
Timer, keyboard response, WM_PAINT will occur in parallel. When one of them is not finished, there is another possibility to start running; even when the timer is not finished, a timer may start. And these parallel code, all call the Block.cpp. Sometimes, for example, one of the coordinates is not finished, and there is a start to refresh the workspace so that there are elements in the workspace that are in a messy position.
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. It's just that I haven't figured out how to design talent to circumvent this knowledge. But I guess it should be similar to the design of the 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 Guess, you should verify it before you change it.
Students usually take a step towards verifying the conjecture and implementing the settlement, which I often do. 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 change the code according to speculation. Assuming the problem is gone, well, they assume that the solution to the problem has been grasped; if the problem is still there, make a guess and then change it immediately. Even worse, did not go back to the beginning of the previous step, in the current work code "continue" changes, so that each conjecture accumulated, and finally solve the problem even do not know what the reason.
The experiment should be designed first, according to the conjecture model, assuming how it will be. After verifying the conjecture, we will solve it. Suppose, for example, that the synchronization of the timer and keyboard events caused the drawing to be confusing, instead of competing for the process of communication, it should be a simple brute to remove the synchronization, with greater granularity as an atomic operation, to verify conjecture. Assuming that the conjecture is correct, the phenomenon should change. Although it affects performance and effectiveness, it is not the code that is ready to be used, it is just a validation of conjecture. When the conjecture is verified, think of a better solution, for example, to create 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 one step forward can be solved, they are at this time change the plan. Ask why. A: Because this technology can't solve this problem.
It is extremely difficult to determine "no", even more difficult than to determine "can". You can't, not to be sure that this method can't.
You need to be fully aware of the technology you are using and have sufficient and clear confidence in the tasks it can complete. At the same time, it should be understood that the solution to the replacement can solve any problem. To do prototype verification, according to theoretical deduction, these are the solutions. To see the tools, to use, piantingpianxin others comments, it is too hasty, once found is not a panacea, turn around to find the means, it is even more hasty.
6.6 Version Number control
In order to allow students to see the development process, I used the file system in class to control the version number, each lesson a folder, sometimes compressed into zip. After the course, a version number of a version number added git, then commit, operation for two hours (? ), and worry about the whole wrong, miserable.
Next time make sure you do the version number control from the very beginning. You also need to manually delete debug, PCH, SDF and other binary garbage before commit.
7. Accessories
The attachment is the code and log that is controlled with the GIT version number, [http://download.csdn.net/detail/younggift/7499881] here.
Protype is the technical prototype.
The Tetris is the Russian block project itself. The previous version number was VS2010, and the last day was VS2012. You can add only the code part into the Win32project to fit your vs version number, or the dev C + + version number.
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, due to the need, so change to Word.
Pic.bmp is a picture, used to illustrate the definition.
Branch is a branch, I forget whether it adds trunk, stay there spare, in case of omission.
--------------------
The blog will be manually synced to the following address:
[Http://giftdotyoung.blogspot.com]
[Http://blog.csdn.net/younggift]