Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: 4DGL-uLCD-SE SDFileSystem mbed-rtos mbed wave_player
main.cpp
00001 00002 //FULL GAME (MAIN MBED) 00003 00004 #include "mbed.h" 00005 #include "uLCD_4DGL.h" 00006 #include "rtos.h" 00007 #include "SDFileSystem.h" 00008 #include <string> 00009 #include <vector> 00010 #include "joystick.h" 00011 #include "background.h" 00012 #include "arrows.h" 00013 #include "releaser.h" 00014 #include "songs.h" 00015 00016 00017 Mutex mutex; 00018 Mutex m; 00019 Mutex arm; 00020 uLCD_4DGL uLCD2(p9,p10,p16); // serial tx, serial rx, reset pin; 00021 uLCD_4DGL uLCD(p28,p27,p29); 00022 SDFileSystem sd(p5, p6, p7, p8, "sd"); 00023 //InterruptIn pb1(p22); 00024 //InterruptIn pb2(p21); 00025 volatile bool up1 = false; 00026 volatile bool down1 = false; 00027 volatile bool left1 = false; 00028 volatile bool right1 = false; 00029 volatile bool fire1 = false; 00030 volatile bool up2 = false; 00031 volatile bool down2 = false; 00032 volatile bool left2 = false; 00033 volatile bool right2 = false; 00034 volatile bool fire2 = false; 00035 volatile int song1 = 0; 00036 volatile int song2 = 0; 00037 Nav_Switch js1( p12, p14, p13, p15, p11, &up1, &down1, &left1, &right1, &fire1,&song1); //Nav_Switch(PinName up,PinName down,PinName left,PinName right,PinName fire); 00038 Nav_Switch js2( p23, p25, p24, p21, p22, &up2, &down2, &left2, &right2, &fire2,&song2); //Nav_Switch(PinName up,PinName down,PinName left,PinName right,PinName fire); 00039 // variables used for controlling the process 00040 volatile bool pushed1 = false; 00041 volatile bool pushed2 = false; 00042 volatile bool selected1 = false; 00043 volatile bool selected2 = false; 00044 volatile bool process1 = false; 00045 volatile bool process2 = false; 00046 volatile bool finished1 = false; 00047 volatile bool finished2 = false; 00048 ArrowReleaser ar = ArrowReleaser(); 00049 ArrowReleaser ar2 = ArrowReleaser(); 00050 double nextdelay = 1.0; 00051 int indx = 0; 00052 int chk; 00053 int chk2; 00054 volatile int counter = 0; 00055 volatile int counter2 = 0; 00056 volatile bool empty = false; 00057 volatile bool scoreScreen = false; 00058 DigitalOut songSelected(p19); 00059 DigitalOut specificSong(p20); 00060 00061 00062 00063 00064 vector<string> filenames; 00065 00066 // helper function 00067 // read files names in dir and put them in the variable "filenames" 00068 void read_file_names(char *dir) 00069 { 00070 DIR *dp; 00071 struct dirent *dirp; 00072 dp = opendir(dir); 00073 //read all directory and file names in current directory into filename vector 00074 while((dirp = readdir(dp)) != NULL) { 00075 string filename = string(dirp->d_name); 00076 if ((filename[0] >= 97 && filename[0] <=122) || (filename[0] >= 65 && filename[0] <=90)) { 00077 filenames.push_back(filename); 00078 } 00079 } 00080 closedir(dp); 00081 } 00082 00083 void uLCD_thread(void const *args) { 00084 00085 // 1. title screen 00086 mutex.lock(); 00087 //uLCD.cls(); 00088 //uLCD.reset(); 00089 uLCD.textbackground_color(BLACK); 00090 uLCD.color(RED); 00091 uLCD.locate(4,0); 00092 uLCD.printf("Dance Dance"); 00093 uLCD.locate(4,1); 00094 uLCD.printf("Revolution!"); 00095 uLCD.locate(6,15); 00096 uLCD.printf("Player 1"); 00097 mutex.unlock(); 00098 while(!pushed1) { 00099 mutex.lock(); 00100 uLCD.locate(4,7); 00101 uLCD.printf("Press CENTER"); 00102 mutex.unlock(); 00103 Thread::wait(1000); 00104 mutex.lock(); 00105 uLCD.locate(4,7); 00106 uLCD.printf(" "); 00107 mutex.unlock(); 00108 pushed1 = fire1; 00109 } 00110 00111 // 2. ready screen 00112 mutex.lock(); 00113 uLCD.cls(); 00114 uLCD.locate(0,7); 00115 uLCD.printf("Player 1 is ready!"); 00116 mutex.unlock(); 00117 Thread::wait(1000); 00118 while(!pushed2) { 00119 mutex.lock(); 00120 uLCD.locate(0,9); 00121 uLCD.printf("Wait player 2..."); 00122 mutex.unlock(); 00123 } 00124 00125 // 3. song selection screen 00126 while(!process1) { 00127 Thread::yield(); 00128 } 00129 mutex.lock(); 00130 uLCD.cls(); 00131 uLCD.printf("Select a song"); 00132 mutex.unlock(); 00133 fire1 = false; 00134 while(!selected1 || !selected2) { 00135 for (int i = 0; i < filenames.size(); i++) { 00136 mutex.lock(); 00137 if (i == song1) { 00138 uLCD.color(RED); 00139 } else { 00140 uLCD.color(GREEN); 00141 } 00142 uLCD.locate(0,i+2); 00143 uLCD.printf("%s\n\r", filenames[i].c_str()); 00144 uLCD.locate(16,i+2); 00145 uLCD.printf(" "); 00146 if (i == song2) { 00147 uLCD.color(BLUE); 00148 uLCD.locate(16,i+2); 00149 uLCD.printf("<-"); 00150 } 00151 mutex.unlock(); 00152 } 00153 if (selected1) { 00154 mutex.lock(); 00155 uLCD.locate(0,8); 00156 uLCD.color(RED); 00157 uLCD.printf("Ready!"); 00158 mutex.unlock(); 00159 } 00160 selected1 = fire1; 00161 } 00162 00163 00164 // 5. count down 00165 mutex.lock(); 00166 uLCD.cls(); 00167 uLCD.color(RED); 00168 uLCD.printf("%s\n\r", filenames[song1].c_str()); 00169 uLCD.locate(0,1); 00170 uLCD.printf("is picked"); 00171 mutex.unlock(); 00172 while(!process2) { 00173 Thread::yield(); 00174 } 00175 mutex.lock(); 00176 uLCD.cls(); 00177 uLCD.text_width(4); //4X size text 00178 uLCD.text_height(4); 00179 mutex.unlock(); 00180 uLCD.color(RED); 00181 for (int i=5; i>=0; --i) { 00182 mutex.lock(); 00183 uLCD.locate(1,2); 00184 uLCD.printf("%2D",i); 00185 mutex.unlock(); 00186 Thread::wait(1000); 00187 } 00188 finished1 = true; 00189 } 00190 00191 void uLCD2_thread(void const *args) { 00192 00193 // 1. title screen 00194 mutex.lock(); 00195 //uuLCD2.cls(); 00196 //uuLCD2.reset(); 00197 uLCD2.textbackground_color(BLACK); 00198 uLCD2.color(BLUE); 00199 uLCD2.locate(4,0); 00200 uLCD2.printf("Dance Dance"); 00201 uLCD2.locate(4,1); 00202 uLCD2.printf("Revolution!"); 00203 uLCD2.locate(6,15); 00204 uLCD2.printf("Player 2"); 00205 mutex.unlock(); 00206 while(!pushed2) { 00207 mutex.lock(); 00208 uLCD2.locate(4,7); 00209 uLCD2.printf("Press CENTER"); 00210 mutex.unlock(); 00211 wait(1); 00212 mutex.lock(); 00213 uLCD2.locate(4,7); 00214 uLCD2.printf(" "); 00215 mutex.unlock(); 00216 pushed2 = fire2; 00217 } 00218 00219 // 2. ready screen 00220 mutex.lock(); 00221 uLCD2.cls(); 00222 uLCD2.locate(0,7); 00223 uLCD2.printf("Player 2 is ready!"); 00224 mutex.unlock(); 00225 Thread::wait(1000); 00226 while(!pushed1){ 00227 mutex.lock(); 00228 uLCD2.locate(0,9); 00229 uLCD2.printf("Wait player 1..."); 00230 mutex.unlock(); 00231 } 00232 00233 // 3. song selection screen 00234 Thread::wait(1000); 00235 process1 = true; 00236 mutex.lock(); 00237 uLCD2.cls(); 00238 uLCD2.printf("Select a song"); 00239 mutex.unlock(); 00240 fire2 = false; 00241 while(!selected2 || !selected1) { 00242 for (int i = 0; i < filenames.size(); i++) { 00243 mutex.lock(); 00244 if (i == song2) { 00245 uLCD2.color(BLUE); 00246 } else { 00247 uLCD2.color(GREEN); 00248 } 00249 uLCD2.locate(0,i+2); 00250 uLCD2.printf("%s\n\r", filenames[i].c_str()); 00251 uLCD2.locate(16,i+2); 00252 uLCD2.printf(" "); 00253 // add a marker to notify player 2 about player 1's selection 00254 if (i == song1) { 00255 uLCD2.color(RED); 00256 uLCD2.locate(16,i+2); 00257 uLCD2.printf("<-"); 00258 } 00259 mutex.unlock(); 00260 } 00261 if (selected2) { 00262 mutex.lock(); 00263 uLCD2.locate(0,8); 00264 uLCD2.color(BLUE); 00265 uLCD2.printf("Ready!"); 00266 mutex.unlock(); 00267 } 00268 selected2 = fire2; 00269 } 00270 00271 // 5. count down 00272 mutex.lock(); 00273 uLCD2.cls(); 00274 uLCD2.color(BLUE); 00275 uLCD2.printf("%s\n\r", filenames[song1].c_str()); 00276 uLCD2.locate(0,1); 00277 uLCD2.printf("is picked"); 00278 mutex.unlock(); 00279 Thread::wait(3000); 00280 process2 = true; 00281 mutex.lock(); 00282 uLCD2.cls(); 00283 uLCD2.text_width(4); //4X size text 00284 uLCD2.text_height(4); 00285 mutex.unlock(); 00286 uLCD2.color(BLUE); 00287 for (int i=5; i>=0; --i) { 00288 mutex.lock(); 00289 uLCD2.locate(1,2); 00290 uLCD2.printf("%2D",i); 00291 mutex.unlock(); 00292 Thread::wait(1000); 00293 } 00294 finished2 = true; 00295 } 00296 00297 //GAME 00298 00299 void arrowreleasing_thread1(void const *args) { 00300 Thread::wait(100); 00301 if(!song1){ 00302 while(1){ 00303 chk = 0; chk2 =0; 00304 while(!chk && !chk2){ 00305 arm.lock(); 00306 m.lock(); 00307 chk = ar.ReleaseArrow(DemoArray2,indx,2); 00308 m.unlock(); 00309 arm.unlock(); 00310 Thread::wait(100); 00311 } 00312 indx++; 00313 if(indx == 53){ 00314 arm.lock(); 00315 ar.setTermStatus(1); 00316 arm.unlock(); 00317 break; 00318 } 00319 nextdelay = DemoArray2[indx-1].getDelay(); 00320 Thread::wait(nextdelay*1000); 00321 } 00322 } 00323 else{ 00324 while(1){ 00325 chk = 0; chk2 =0; 00326 while(!chk && !chk2){ 00327 arm.lock(); 00328 m.lock(); 00329 chk = ar.ReleaseArrow(DemoArray,indx,2); 00330 m.unlock(); 00331 arm.unlock(); 00332 Thread::wait(100); 00333 } 00334 indx++; 00335 if(indx == 53){ 00336 arm.lock(); 00337 ar.setTermStatus(1); 00338 arm.unlock(); 00339 break; 00340 } 00341 nextdelay = DemoArray[indx-1].getDelay(); 00342 Thread::wait(nextdelay*1000); 00343 } 00344 } 00345 } 00346 00347 void arrowdrawing_thread1(void const *args){ 00348 int term; 00349 while(!scoreScreen){ 00350 for(int i=0; i< 5; i++){ 00351 arm.lock(); 00352 if(ar.ActiveArrows[i].getActive()){ 00353 m.lock(); 00354 term = ar.ActiveArrows[i].update(2); 00355 m.unlock(); 00356 if(term){ 00357 ar.ActiveArrows[i].setActive(0); 00358 m.lock(); 00359 setup_goal_arrows(); 00360 uLCD.locate(0,0); 00361 uLCD.printf("%d",counter); 00362 setup_goal_arrows2(); 00363 uLCD2.locate(0,0); 00364 uLCD2.printf("%d",counter2); 00365 m.unlock(); 00366 term = 0; 00367 } 00368 } 00369 arm.unlock(); 00370 } 00371 /*arm.lock(); 00372 if(empty && ar.getTermStatus()){ 00373 arm.unlock(); 00374 //Song has ended and all arrows have ended 00375 scoreScreen = true; 00376 break; 00377 }*/ 00378 } 00379 00380 } 00381 00382 00383 void joystick2_thread(void const *args) { 00384 while(!scoreScreen){ 00385 if(left2){ 00386 left2 = false; 00387 for(int i=0; i < 5; i++){ 00388 arm.lock(); 00389 if(ar.ActiveArrows[i].getActive() && ar.ActiveArrows[i].getType() == 0 && abs(ar.ActiveArrows[i].curry - refarrow_left_y) <= 10){ 00390 arm.unlock(); 00391 counter2++; 00392 m.lock(); 00393 uLCD2.locate(0,0); 00394 uLCD2.printf("%d",counter2); 00395 m.unlock(); 00396 break; 00397 } 00398 arm.unlock(); 00399 } 00400 m.lock(); 00401 uLCD2.circle(refarrow_left_x,refarrow_left_y,10,0x00FF00); 00402 m.unlock(); 00403 wait(0.04); 00404 m.lock(); 00405 uLCD2.circle(refarrow_left_x,refarrow_left_y,10,0x000000); 00406 uLCD2.locate(0,0); 00407 uLCD2.printf("%d",counter2); 00408 m.unlock(); 00409 } 00410 if(right2){ 00411 right2 = false; 00412 for(int i=0; i < 5; i++){ 00413 arm.lock(); 00414 if(ar.ActiveArrows[i].getActive() && ar.ActiveArrows[i].getType() == 3 && abs(ar.ActiveArrows[i].curry - refarrow_right_y) <= 10){ 00415 arm.unlock(); 00416 counter2++; 00417 m.lock(); 00418 uLCD2.locate(0,0); 00419 uLCD2.printf("%d",counter2); 00420 m.unlock(); 00421 break; 00422 } 00423 arm.unlock(); 00424 } 00425 m.lock(); 00426 uLCD2.circle(refarrow_right_x,refarrow_right_y,10,0x00FF00); 00427 m.unlock(); 00428 wait(0.04); 00429 m.lock(); 00430 uLCD2.circle(refarrow_right_x,refarrow_right_y,10,0x000000); 00431 uLCD2.locate(0,0); 00432 uLCD2.printf("%d",counter2); 00433 m.unlock(); 00434 } 00435 if(up2){ 00436 up2 = false; 00437 for(int i=0; i < 5; i++){ 00438 arm.lock(); 00439 if(ar.ActiveArrows[i].getActive() && ar.ActiveArrows[i].getType() == 1 && abs(ar.ActiveArrows[i].curry - refarrow_up_y) <= 10){ 00440 arm.unlock(); 00441 counter2++; 00442 m.lock(); 00443 uLCD2.locate(0,0); 00444 uLCD2.printf("%d",counter2); 00445 m.unlock(); 00446 break; 00447 } 00448 arm.unlock(); 00449 } 00450 m.lock(); 00451 uLCD2.circle(refarrow_up_x,refarrow_up_y,10,0x00FF00); 00452 m.unlock(); 00453 wait(0.04); 00454 m.lock(); 00455 uLCD2.circle(refarrow_up_x,refarrow_up_y,10,0x000000); 00456 uLCD2.locate(0,0); 00457 uLCD2.printf("%d",counter2); 00458 m.unlock(); 00459 } 00460 if(down2){ 00461 down2 = false; 00462 for(int i=0; i < 5; i++){ 00463 arm.lock(); 00464 if(ar.ActiveArrows[i].getActive() && ar.ActiveArrows[i].getType() == 2 && abs(ar.ActiveArrows[i].curry - refarrow_down_y) <= 10){ 00465 arm.unlock(); 00466 counter2++; 00467 m.lock(); 00468 uLCD2.locate(0,0); 00469 uLCD2.printf("%d",counter2); 00470 m.unlock(); 00471 break; 00472 } 00473 arm.unlock(); 00474 } 00475 m.lock(); 00476 uLCD2.circle(refarrow_down_x,refarrow_down_y,10,0x00FF00); 00477 m.unlock(); 00478 wait(0.04); 00479 m.lock(); 00480 uLCD2.circle(refarrow_down_x,refarrow_down_y,10,0x000000); 00481 uLCD2.locate(0,0); 00482 uLCD2.printf("%d",counter2); 00483 m.unlock(); 00484 } 00485 Thread::wait(10); 00486 00487 } 00488 } 00489 00490 00491 int main() 00492 { 00493 00494 uLCD.baudrate(3000000); 00495 uLCD2.baudrate(3000000); 00496 read_file_names("/sd/wavfiles"); 00497 Thread thread1(uLCD_thread); 00498 Thread thread2(uLCD2_thread); 00499 while(true) 00500 { 00501 if(finished1 && finished2){ 00502 thread1.terminate(); 00503 thread2.terminate(); 00504 uLCD.cls(); 00505 uLCD2.cls(); 00506 break; 00507 } 00508 } 00509 00510 songSelected = 1; 00511 specificSong = song1; 00512 00513 //GAME 00514 uLCD.baudrate(8000); 00515 uLCD2.baudrate(8000); 00516 uLCD.color(GREEN); 00517 uLCD2.color(GREEN); 00518 uLCD.locate(0,0); 00519 uLCD.printf("%d",counter); 00520 setup_lining(); 00521 setup_goal_arrows(); 00522 uLCD2.locate(0,0); 00523 uLCD2.printf("%d",counter); 00524 setup_lining2(); 00525 setup_goal_arrows2(); 00526 wait(1); 00527 Thread t1(arrowreleasing_thread1); 00528 t1.set_priority(osPriorityRealtime); 00529 Thread t2(arrowdrawing_thread1); 00530 Thread t3(joystick2_thread); 00531 t3.set_priority(osPriorityHigh); 00532 while(1){ 00533 00534 if(left1){ 00535 left1 = false; 00536 for(int i=0; i < 5; i++){ 00537 arm.lock(); 00538 if(ar.ActiveArrows[i].getActive() && ar.ActiveArrows[i].getType() == 0 && abs(ar.ActiveArrows[i].curry - refarrow_left_y) <= 10){ 00539 arm.unlock(); 00540 counter++; 00541 m.lock(); 00542 uLCD.locate(0,0); 00543 uLCD.printf("%d",counter); 00544 m.unlock(); 00545 break; 00546 } 00547 arm.unlock(); 00548 } 00549 m.lock(); 00550 uLCD.circle(refarrow_left_x,refarrow_left_y,10,0x00FF00); 00551 m.unlock(); 00552 wait(0.04); 00553 m.lock(); 00554 uLCD.circle(refarrow_left_x,refarrow_left_y,10,0x000000);uLCD.locate(0,0); 00555 uLCD.locate(0,0); 00556 uLCD.printf("%d",counter); 00557 m.unlock(); 00558 } 00559 if(right1){ 00560 right1 = false; 00561 for(int i=0; i < 5; i++){ 00562 arm.lock(); 00563 if(ar.ActiveArrows[i].getActive() && ar.ActiveArrows[i].getType() == 3 && abs(ar.ActiveArrows[i].curry - refarrow_right_y) <= 10){ 00564 arm.unlock(); 00565 counter++; 00566 m.lock(); 00567 uLCD.locate(0,0); 00568 uLCD.printf("%d",counter); 00569 m.unlock(); 00570 break; 00571 } 00572 arm.unlock(); 00573 } 00574 m.lock(); 00575 uLCD.circle(refarrow_right_x,refarrow_right_y,10,0x00FF00); 00576 m.unlock(); 00577 wait(0.04); 00578 m.lock(); 00579 uLCD.circle(refarrow_right_x,refarrow_right_y,10,0x000000); 00580 uLCD.locate(0,0); 00581 uLCD.printf("%d",counter); 00582 m.unlock(); 00583 } 00584 if(up1){ 00585 up1 = false; 00586 for(int i=0; i < 5; i++){ 00587 arm.lock(); 00588 if(ar.ActiveArrows[i].getActive() && ar.ActiveArrows[i].getType() == 1 && abs(ar.ActiveArrows[i].curry - refarrow_up_y) <= 10){ 00589 arm.unlock(); 00590 counter++; 00591 m.lock(); 00592 uLCD.locate(0,0); 00593 uLCD.printf("%d",counter); 00594 m.unlock(); 00595 break; 00596 } 00597 arm.unlock(); 00598 } 00599 m.lock(); 00600 uLCD.circle(refarrow_up_x,refarrow_up_y,10,0x00FF00); 00601 m.unlock(); 00602 wait(0.04); 00603 m.lock(); 00604 uLCD.circle(refarrow_up_x,refarrow_up_y,10,0x000000); 00605 uLCD.locate(0,0); 00606 uLCD.printf("%d",counter); 00607 m.unlock(); 00608 } 00609 if(down1){ 00610 down1 = false; 00611 for(int i=0; i < 5; i++){ 00612 arm.lock(); 00613 if(ar.ActiveArrows[i].getActive() && ar.ActiveArrows[i].getType() == 2 && abs(ar.ActiveArrows[i].curry - refarrow_down_y) <= 10){ 00614 arm.unlock(); 00615 counter++; 00616 m.lock(); 00617 uLCD.locate(0,0); 00618 uLCD.printf("%d",counter); 00619 m.unlock(); 00620 break; 00621 } 00622 arm.unlock(); 00623 } 00624 m.lock(); 00625 uLCD.circle(refarrow_down_x,refarrow_down_y,10,0x00FF00); 00626 m.unlock(); 00627 wait(0.04); 00628 m.lock(); 00629 uLCD.circle(refarrow_down_x,refarrow_down_y,10,0x000000); 00630 uLCD.locate(0,0); 00631 uLCD.printf("%d",counter); 00632 m.unlock(); 00633 } 00634 if(fire1){ 00635 //nothing 00636 } 00637 arm.lock(); 00638 if(ar.getTermStatus()){ 00639 empty = true; 00640 for(int i=0; i < 5; i++){ 00641 if(ar.ActiveArrows[i].getActive()) { 00642 empty = false; 00643 break; 00644 } 00645 } 00646 if(empty) { 00647 scoreScreen = true; 00648 uLCD.cls(); 00649 uLCD2.cls(); 00650 break; 00651 } 00652 } 00653 arm.unlock(); 00654 wait(0.01); 00655 } 00656 00657 //End of game 00658 uLCD.cls(); 00659 uLCD2.cls(); 00660 if(counter > counter2){ 00661 end_screen(); 00662 uLCD.locate(6,6); 00663 uLCD.color(GREEN); 00664 uLCD.printf("YOU WIN!\n PLAYER 1 WINS!"); 00665 uLCD2.locate(5,6); 00666 uLCD2.color(RED); 00667 uLCD2.printf("YOU LOSE!\n PLAYER 1 WINS!"); 00668 end_screen(); 00669 } 00670 else if (counter2 > counter) { 00671 end_screen(); 00672 uLCD.locate(5,6); 00673 uLCD.color(RED); 00674 uLCD.printf("YOU LOSE!\n PLAYER 2 WINS!"); 00675 uLCD2.locate(6,6); 00676 uLCD.color(GREEN); 00677 uLCD2.printf("YOU WIN!\n PLAYER 2 WINS!"); 00678 end_screen(); 00679 } 00680 else { 00681 end_screen(); 00682 uLCD.locate(7,7); 00683 uLCD.printf("DRAW"); 00684 uLCD2.locate(7,7); 00685 uLCD2.printf("DRAW"); 00686 end_screen(); 00687 } 00688 wait(5); 00689 uLCD.cls(); 00690 uLCD2.cls(); 00691 end_screen(); 00692 uLCD.locate(6,6); 00693 uLCD.color(GREEN); 00694 uLCD.printf("Thanks\n for playing!"); 00695 uLCD2.locate(6,6); 00696 uLCD2.color(GREEN); 00697 uLCD2.printf("Thanks\n for playing!"); 00698 end_screen(); 00699 }
Generated on Tue Jul 12 2022 21:13:59 by
1.7.2