]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_tetris.qc
tetris: optimizations...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_tetris.qc
1 /*
2
3 Installation:
4
5 compile with -DTETRIS
6
7 */
8
9 #ifdef TETRIS
10
11 float autocvar_g_bastet;
12 .vector tet_org;
13
14 float tet_vs_current_id;
15 float tet_vs_current_timeout;
16 .float tet_vs_id, tet_vs_addlines;
17 .float tet_highest_line;
18 .float tetris_on, tet_gameovertime, tet_drawtime, tet_autodown;
19 .vector piece_pos;
20 .float piece_type, next_piece, tet_score, tet_lines;
21 .float tet_piece_bucket;
22
23 // tetris_on states:
24 //   1 = running
25 //   2 = game over
26 //   3 = waiting for VS players
27
28 var float tet_high_score = 0;
29
30 vector TET_START_PIECE_POS = '5 1 0';
31 float TET_LINES = 22;
32 float TET_DISPLAY_LINES = 20;
33 float TET_WIDTH = 10;
34 //character values
35 float TET_BORDER = 139;
36 float TET_BLOCK = 133;
37 float TET_SPACE = 160; // blankness
38
39
40
41 float TETKEY_UP = 1;
42 float TETKEY_DOWN = 2;
43 float TETKEY_LEFT = 4;
44 float TETKEY_RIGHT = 8;
45 float TETKEY_ROTLEFT = 16;
46 float TETKEY_ROTRIGHT = 32;
47 float TETKEY_DROP = 64;
48 string TET_PADDING_RIGHT = "\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0"; // get away from crosshair
49
50 float PIECES = 7;
51
52 .float line1, line2, line3, line4, line5, line6, line7,
53 line8, line9, line10, line11, line12, line13, line14, line15,
54 line16, line17, line18, line19, line20, line21, line22;
55
56
57 float   SVC_CENTERPRINTa                = 26;
58
59 float Tetris_Level()
60 {
61         return ((floor((self.tet_lines / 10)) + 1));
62 };
63
64 void tetsnd(string snd)
65 {
66         play2(self, strcat("sounds/tetris/", snd));
67 }
68
69 /*
70 *********************************
71
72 Library Functions
73
74 *********************************
75 */
76 void SetLine(float ln, float vl)
77 {
78         if (ln == 1)
79                 self.line1 = vl;
80         else if (ln == 2)
81                 self.line2 = vl;
82         else if (ln == 3)
83                 self.line3 = vl;
84         else if (ln == 4)
85                 self.line4 = vl;
86         else if (ln == 5)
87                 self.line5 = vl;
88         else if (ln == 6)
89                 self.line6 = vl;
90         else if (ln == 7)
91                 self.line7 = vl;
92         else if (ln == 8)
93                 self.line8 = vl;
94         else if (ln == 9)
95                 self.line9 = vl;
96         else if (ln == 10)
97                 self.line10 = vl;
98         else if (ln == 11)
99                 self.line11 = vl;
100         else if (ln == 12)
101                 self.line12 = vl;
102         else if (ln == 13)
103                 self.line13 = vl;
104         else if (ln == 14)
105                 self.line14 = vl;
106         else if (ln == 15)
107                 self.line15 = vl;
108         else if (ln == 16)
109                 self.line16 = vl;
110         else if (ln == 17)
111                 self.line17 = vl;
112         else if (ln == 18)
113                 self.line18 = vl;
114         else if (ln == 19)
115                 self.line19 = vl;
116         else if (ln == 20)
117                 self.line20 = vl;
118         else if (ln == 21)
119                 self.line21 = vl;
120         else if (ln == 22)
121                 self.line22 = vl;
122 };
123
124 float GetLine(float ln)
125 {
126         if (ln == 1)
127                 return self.line1;
128         else if (ln == 2)
129                 return self.line2;
130         else if (ln == 3)
131                 return self.line3;
132         else if (ln == 4)
133                 return self.line4;
134         else if (ln == 5)
135                 return self.line5;
136         else if (ln == 6)
137                 return self.line6;
138         else if (ln == 7)
139                 return self.line7;
140         else if (ln == 8)
141                 return self.line8;
142         else if (ln == 9)
143                 return self.line9;
144         else if (ln == 10)
145                 return self.line10;
146         else if (ln == 11)
147                 return self.line11;
148         else if (ln == 12)
149                 return self.line12;
150         else if (ln == 13)
151                 return self.line13;
152         else if (ln == 14)
153                 return self.line14;
154         else if (ln == 15)
155                 return self.line15;
156         else if (ln == 16)
157                 return self.line16;
158         else if (ln == 17)
159                 return self.line17;
160         else if (ln == 18)
161                 return self.line18;
162         else if (ln == 19)
163                 return self.line19;
164         else if (ln == 20)
165                 return self.line20;
166         else if (ln == 21)
167                 return self.line21;
168         else if (ln == 22)
169                 return self.line22;
170         else
171                 return 0;
172 };
173
174 float GetXBlock(float x, float dat)
175 {
176         if (x == 1)
177                 return dat & 3;
178         else if (x == 2)
179                 return (dat & 12) / 4;
180         else if (x == 3)
181                 return (dat & 48) / 16;
182         else if (x == 4)
183                 return (dat & 192) / 64;
184         else if (x == 5)
185                 return (dat & 768) / 256;
186         else if (x == 6)
187                 return (dat & 3072) / 1024;
188         else if (x == 7)
189                 return (dat & 12288) / 4096;
190         else if (x == 8)
191                 return (dat & 49152) / 16384;
192         else if (x == 9)
193                 return (dat & 196608) / 65536;
194         else if (x == 10)
195                 return (dat & 786432) / 262144;
196         else
197                 return 0;
198 };
199
200 float SetXBlock(float x, float dat, float new)
201 {
202         if (x == 1)
203                 return (dat - (dat & 3)) | new;
204         else if (x == 2)
205                 return (dat - (dat & 12)) | (new*4);
206         else if (x == 3)
207                 return (dat - (dat & 48)) | (new*16);
208         else if (x == 4)
209                 return (dat - (dat & 192)) | (new*64);
210         else if (x == 5)
211                 return (dat - (dat & 768)) | (new*256);
212         else if (x == 6)
213                 return (dat - (dat & 3072)) | (new*1024);
214         else if (x == 7)
215                 return (dat - (dat & 12288)) | (new*4096);
216         else if (x == 8)
217                 return (dat - (dat & 49152)) | (new*16384);
218         else if (x == 9)
219                 return (dat - (dat & 196608)) | (new*65536);
220         else if (x == 10)
221                 return (dat - (dat & 786432)) | (new*262144);
222         else
223                 return dat;
224 };
225
226
227 float GetSquare(float x, float y)
228 {
229         return GetXBlock(x,  GetLine(y));
230 };
231
232 void SetSquare(float x, float y, float val)
233 {
234         float dat;
235
236         dat = GetLine(y);
237         dat  = SetXBlock(x, dat, val & 3);
238         SetLine(y, dat);
239 };
240
241 vector PieceShape(float pc)
242 {
243         if (pc == 1)
244                 return '20 20 0'; // O
245         else if (pc == 2)
246                 return '1 21 0'; // J
247         else if (pc == 3)
248                 return '16 21 0'; // L
249         else if (pc == 4)
250                 return '85 0 0'; // I
251         else if (pc == 5)
252                 return '5 20 0'; // Z
253         else if (pc == 6)
254                 return '20 5 0'; // S
255         else if (pc == 7)
256                 return '4 21 0'; // T
257         else
258                 return '0 0 0';
259 }
260 vector PieceSize(float pc)
261 {
262         if (pc == 1)
263                 return '2 2 0'; // O
264         else if (pc == 2)
265                 return '3 2 0'; // J
266         else if (pc == 3)
267                 return '3 2 0'; // L
268         else if (pc == 4)
269                 return '4 1 0'; // I
270         else if (pc == 5)
271                 return '3 2 0'; // Z
272         else if (pc == 6)
273                 return '3 2 0'; // S
274         else if (pc == 7)
275                 return '3 2 0'; // T
276         else
277                 return '0 0 0';
278 }
279 vector PieceCenter(float pc)
280 {
281         if(pc == 1)
282                 return '2.5 1.5 0'; // O
283         else if (pc == 2)
284                 return '2 2 0'; // J
285         else if (pc == 3)
286                 return '2 2 0'; // L
287         else if (pc == 4)
288                 return '2.5 1.5 0'; // I
289         else if (pc == 5)
290                 return '2 2 0'; // Z
291         else if (pc == 6)
292                 return '2 2 0'; // S
293         else if (pc == 7)
294                 return '2 2 0'; // T
295         else
296                 return '0 0 0';
297 }
298
299 // do x 1..4 and y 1..4 in case of rotation
300 float PieceMetric(float x, float y, float rot, float pc)
301 {
302         float t;
303         vector ce;
304
305         // return bits of a piece
306         ce = PieceCenter(pc);
307         if (rot == 1) // 90 degrees
308         {
309                 // x+cx, y+cy -> -y+cx, x+cy
310                 // X, Y       -> -Y+cy+cx, X-cx+cy
311                 //   x = X-cx
312                 //   y = Y-cy
313                 t = y;
314                 y = x - ce_x + ce_y;
315                 x = -t + ce_x + ce_y;
316         }
317         else if (rot == 2)//180
318         {
319                 x = 2 * ce_x - x;
320                 y = 2 * ce_y - y;
321         }
322         else if (rot == 3) // 270
323         {
324                 // x+cx, y+cy -> y+cx, -x+cy
325                 // X, Y       -> Y-cy+cx, -X+cx+cy
326                 //   x = X-cx
327                 //   y = Y-cy
328                 t = y;
329                 y = -x + ce_y + ce_x;
330                 x =  t - ce_y + ce_x;
331         }
332         if (x < 1 || y < 1 || x > 4 || y > 2)
333                 return 0;
334         ce = PieceShape(pc);
335         if (y == 1)
336                 return GetXBlock(x, ce_x); // first row
337         else if (y == 2)
338                 return GetXBlock(x, ce_y); // second row
339         else
340                 return 0; // illegal parms
341 };
342 vector tet_piecemins;
343 vector tet_piecemaxs;
344 void PieceMinsMaxs(float rot, float pc)
345 {
346         vector sz, ce;
347         float t;
348         vector v;
349
350         sz = PieceSize(pc);
351         ce = PieceCenter(pc);
352         // 1 = 2..2
353         // 2 = 2..3
354         // 3 = 1..3
355         // 4 = 1..4
356         tet_piecemins_x = floor(2.5 - sz_x * 0.5);
357         tet_piecemaxs_x = floor(2.0 + sz_x * 0.5);
358         tet_piecemins_y = 1;
359         tet_piecemaxs_y = sz_y;
360         if (rot == 1) // 90 degrees
361         {
362                 t = tet_piecemins_y;
363                 tet_piecemins_y = -tet_piecemins_x + ce_y + ce_x;
364                 tet_piecemins_x = t - ce_y + ce_x;
365                 t = tet_piecemaxs_y;
366                 tet_piecemaxs_y = -tet_piecemaxs_x + ce_y + ce_x;
367                 tet_piecemaxs_x = t - ce_y + ce_x;
368                 // swap mins_y, maxs_y
369                 t = tet_piecemins_y;
370                 tet_piecemins_y = tet_piecemaxs_y;
371                 tet_piecemaxs_y = t;
372                 // TODO OPTIMIZE
373         }
374         else if (rot == 2)//180
375         {
376                 v = tet_piecemins;
377                 tet_piecemins = 2 * ce - tet_piecemaxs;
378                 tet_piecemaxs = 2 * ce - v;
379         }
380         else if (rot == 3) // 270
381         {
382                 t = tet_piecemins_y;
383                 tet_piecemins_y = tet_piecemins_x - ce_x + ce_y;
384                 tet_piecemins_x = -t + ce_x + ce_y;
385                 t = tet_piecemaxs_y;
386                 tet_piecemaxs_y = tet_piecemaxs_x - ce_x + ce_y;
387                 tet_piecemaxs_x = -t + ce_x + ce_y;
388                 // swap mins_x, maxs_x
389                 t = tet_piecemins_x;
390                 tet_piecemins_x = tet_piecemaxs_x;
391                 tet_piecemaxs_x = t;
392                 // TODO OPTIMIZE
393         }
394 #ifdef VERIFY
395         print(vtos(tet_piecemins), "-");
396         print(vtos(tet_piecemaxs), "\n");
397         if(tet_piecemins_x > tet_piecemaxs_x)
398                 error("inconsistent mins/maxs");
399         if(tet_piecemins_y > tet_piecemaxs_y)
400                 error("inconsistent mins/maxs");
401         float i, j;
402         for(i = 1; i <= 4; ++i)
403                 for(j = 1; j <= 4; ++j)
404                         if(PieceMetric(i, j, rot, pc))
405                                 if(i < tet_piecemins_x || i > tet_piecemaxs_x || j < tet_piecemins_y || j > tet_piecemaxs_y)
406                                         error(sprintf("incorrect mins/maxs: %d %d in %d rot %d mins %v maxs %v\n", i, j, rot, pc, tet_piecemins, tet_piecemaxs));
407 #endif
408 }
409 /*
410 *********************************
411
412 Draw
413
414 *********************************
415 */
416
417
418 /* some prydon gate functions to make life easier....
419
420 somewhat modified because we don't need all the fanciness Prydon Gate is capable of
421
422 */
423
424 void WriteTetrisString(string s)
425 {
426         WriteUnterminatedString(MSG_ONE, strconv(0, 2, 2, s));
427 }
428
429 float pnum(float num, float dig)
430 {
431         local float f, i;
432         if (num < 0)
433         {
434                 WriteChar(MSG_ONE, 173);
435                 num = 0 - num;
436         }
437         f = floor(num / 10);
438         num = num - (f * 10);
439         if (f)
440                 dig = pnum(f, dig+1);
441         else
442         {
443                 // pad to 6
444                 for (i = 0; i < (5 - dig); i = i + 1)
445                         WriteChar(MSG_ONE, TET_SPACE);
446         }
447         WriteChar(MSG_ONE, 176 + num);
448         return dig;
449 };
450
451 void DrawLine(float ln)
452 {
453         float x, d;
454         WriteChar(MSG_ONE, TET_BORDER);
455
456         for (x = 1; x <= TET_WIDTH; x = x + 1)
457         {
458                 d = GetSquare(x, ln + TET_LINES - TET_DISPLAY_LINES);
459                 if (d)
460                 {
461                         WriteChar(MSG_ONE, '^');
462                         WriteChar(MSG_ONE, d * d - 2 * d + 50); // 1, 2, 5
463                         WriteChar(MSG_ONE, TET_BLOCK);
464                 }
465                 else
466                         WriteChar(MSG_ONE, TET_SPACE);
467         }
468         WriteChar(MSG_ONE, '^');
469         WriteChar(MSG_ONE, '7');
470         WriteChar(MSG_ONE, TET_BORDER);
471 }
472
473 void DrawPiece(float pc, float ln)
474 {
475         float x, d, piece_ln, pcolor;
476         vector piece_dat;
477         pcolor = mod(pc, 3) + 1;
478         WriteChar(MSG_ONE, TET_SPACE); // pad to 6
479
480         piece_dat = PieceShape(pc);
481         if (ln == 1)
482                 piece_ln = piece_dat_x;
483         else
484                 piece_ln = piece_dat_y;
485         for (x = 1; x <= 4; x = x + 1)
486         {
487                 d = GetXBlock(x, piece_ln) * pcolor;
488                 if (d)
489                 {
490                         WriteChar(MSG_ONE, '^');
491                         WriteChar(MSG_ONE, d * d - 2 * d + 50); // 1, 2, 5
492                         WriteChar(MSG_ONE, TET_BLOCK);
493                 }
494                 else
495                         WriteChar(MSG_ONE, TET_SPACE);
496         }
497         WriteChar(MSG_ONE, TET_SPACE);  // pad to 6
498 }
499 void Draw_Tetris()
500 {
501         float i;
502         entity head;
503         msg_entity = self;
504         WriteChar(MSG_ONE, SVC_CENTERPRINTa);
505         if(autocvar_g_bastet)
506         {
507                 WriteTetrisString("NEVER GONNA GIVE YOU");
508                 WriteChar(MSG_ONE, 10);
509         }
510         // decoration
511         for (i = 1; i <= (TET_WIDTH + 2); i = i + 1)
512                 WriteChar(MSG_ONE, TET_BORDER);
513         WriteTetrisString("      ");
514         WriteUnterminatedString(MSG_ONE, TET_PADDING_RIGHT);
515         WriteChar(MSG_ONE, 10);
516         for (i = 1; i <= TET_DISPLAY_LINES; i = i + 1)
517         {
518                 if(self.tetris_on == 2)
519                         WriteTetrisString(" GAME  OVER ");
520                 else if(self.tetris_on == 3)
521                         WriteTetrisString("PLEASE  WAIT");
522                 else
523                         DrawLine(i);
524                 if (i == 1)
525                         WriteTetrisString(autocvar_g_bastet ? " THAT " : " NEXT ");
526                 else if (i == 3)
527                         DrawPiece(self.next_piece, 1);
528                 else if (i == 4)
529                         DrawPiece(self.next_piece, 2);
530                 else if (i == 6)
531                         WriteTetrisString(" LINES");
532                 else if (i == 7)
533                         pnum(self.tet_lines, 0);
534                 else if (i == 9)
535                         WriteTetrisString(" SCORE");
536                 else if (i == 10)
537                         pnum(self.tet_score, 0);
538                 else if (i == 12)
539                         WriteTetrisString(" HIGH ");
540                 else if (i == 13)
541                         WriteTetrisString(" SCORE");
542                 else if (i == 14)
543                         pnum(tet_high_score, 0);
544                 else if (i == 16)
545                         WriteTetrisString(" LEVEL");
546                 else if (i == 17)
547                         pnum(Tetris_Level(), 0);
548                 else
549                         WriteTetrisString("      ");
550                 WriteUnterminatedString(MSG_ONE, TET_PADDING_RIGHT);
551                 WriteChar(MSG_ONE, 10);
552         }
553         // decoration
554
555         for (i = 1; i <= (TET_WIDTH + 2); i = i + 1)
556                 WriteChar(MSG_ONE, TET_BORDER);
557         WriteTetrisString("      ");
558         WriteUnterminatedString(MSG_ONE, TET_PADDING_RIGHT);
559         WriteChar(MSG_ONE, 10);
560
561         // VS game status
562         if(self.tet_vs_id)
563         {
564                 WriteChar(MSG_ONE, 10);
565                 WriteChar(MSG_ONE, 10);
566                 if(self.tetris_on == 3)
567                 {
568                         WriteUnterminatedString(MSG_ONE, strcat("WAITING FOR OTHERS (", ftos(ceil(tet_vs_current_timeout - time)), " SEC)\n"));
569                 }
570
571                 WriteChar(MSG_ONE, 10);
572                 FOR_EACH_REALCLIENT(head) if(head.tetris_on) if(head.tet_vs_id == self.tet_vs_id)
573                 {
574                         if(head == self)
575                                 WriteUnterminatedString(MSG_ONE, ">");
576                         else
577                                 WriteUnterminatedString(MSG_ONE, " ");
578                         if(head.tetris_on == 2)
579                                 WriteUnterminatedString(MSG_ONE, "   X_X");
580                         else
581                                 pnum(head.tet_highest_line, 0);
582                         WriteUnterminatedString(MSG_ONE, " ");
583                         WriteUnterminatedString(MSG_ONE, head.netname);
584                         WriteChar(MSG_ONE, 10);
585                 }
586         }
587
588         WriteChar(MSG_ONE, 0);
589 }
590 /*
591 *********************************
592
593 Game Functions
594
595 *********************************
596 */
597
598 // reset the game
599 void ResetTetris()
600 {
601         float i;
602
603         for (i=1; i<=TET_LINES; i = i + 1)
604                 SetLine(i, 0);
605         self.piece_pos = '0 0 0';
606         self.piece_type = 0;
607         self.next_piece = self.tet_lines = self.tet_score = 0;
608         self.tet_piece_bucket = 0;
609 };
610
611 void Tet_GameExit()
612 {
613         centerprint(self, " ");
614         self.tetris_on = 0;
615         self.tet_vs_id = 0;
616         ResetTetris();
617         self.movetype = MOVETYPE_WALK;
618 };
619
620 void PrintField()
621 {
622         float l;
623         float r, c;
624         for(r = 1; r <= TET_LINES; ++r)
625         {
626                 l = GetLine(r);
627                 print(">");
628                 for(c = 1; c <= TET_WIDTH; ++c)
629                 {
630                         print(ftos(GetXBlock(c, l)));
631                 }
632                 print("\n");
633         }
634 }
635
636 float BastetEvaluate()
637 {
638         float height;
639         float score;
640         float occupied;
641         float occupied_count;
642         float l, lines;
643         float score_save, occupied_save, occupied_count_save;
644         float i, j, line;
645
646         score = 0;
647
648         // adds a bonus for each free dot above the occupied blocks profile
649         occupied_count = TET_WIDTH;
650         height = 0;
651         lines = 0;
652         for(i = 1; i <= TET_LINES; ++i)
653         {
654                 l = GetLine(i);
655                 if(l == 0)
656                 {
657                         height = i;
658                         continue;
659                 }
660                 line = 1;
661                 occupied_save = occupied;
662                 occupied_count_save = occupied_count;
663                 score_save = score;
664                 for(j = 1; j <= TET_WIDTH; ++j)
665                 {
666                         if(GetXBlock(j, l))
667                         {
668                                 if(!GetXBlock(j, occupied))
669                                 {
670                                         occupied = SetXBlock(j, occupied, 1);
671                                         --occupied_count;
672                                 }
673                         }
674                         else
675                                 line = 0;
676                         score += 10000 * occupied_count;
677                 }
678                 if(line)
679                 {
680                         occupied = occupied_save;
681                         occupied_count = occupied_count_save;
682                         score = score_save + 100000000 + 10000 * TET_WIDTH + 1000;
683                         ++lines;
684                 }
685         }
686
687         score += 1000 * height;
688
689         return score;
690 }
691
692 float CheckMetrics(float piece, float orgx, float orgy, float rot);
693 void ClearPiece(float piece, float orgx, float orgy, float rot);
694 void CementPiece(float piece, float orgx, float orgy, float rot);
695 float bastet_profile_evaluate_time;
696 float bastet_profile_checkmetrics_time;
697 float BastetSearch(float buf, float pc, float x, float y, float rot, float move_bias)
698 // returns best score, or -1 if position is impossible
699 {
700         string r;
701         float b;
702         float s, sm;
703         float t1, t2;
704
705         if(move_bias < 0)
706                 return 0; // DO NOT WANT
707
708         if(x < 1 || x > TET_WIDTH || y < 1 || y > TET_LINES)
709                 return -1; // impossible
710         if(rot < 0) rot = 3;
711         if(rot > 3) rot = 0;
712
713         // did we already try?
714         b = x + (TET_WIDTH+2) * (y + (TET_LINES+2) * rot);
715         r = bufstr_get(buf, b);
716         if(r != "") // already tried
717                 return stof(r);
718
719         bufstr_set(buf, b, "0"); // in case we READ that, not that bad - we already got that value in another branch then anyway
720
721
722
723         t1 = gettime(GETTIME_HIRES);
724         if(CheckMetrics(pc, x, y, rot))
725         {
726                 t2 = gettime(GETTIME_HIRES);
727                 bastet_profile_checkmetrics_time += (t2 - t1);
728                 // try all moves
729                 sm = 1;
730                 s = BastetSearch(buf, pc, x-1, y, rot, move_bias - 1); if(s > sm) sm = s;
731                 s = BastetSearch(buf, pc, x+1, y, rot, move_bias - 1); if(s > sm) sm = s;
732                 s = BastetSearch(buf, pc, x, y, rot+1, move_bias - 1); if(s > sm) sm = s;
733                 s = BastetSearch(buf, pc, x, y, rot-1, move_bias - 1); if(s > sm) sm = s;
734
735                 s = BastetSearch(buf, pc, x, y+1, rot, move_bias + 2); if(s > sm) sm = s;
736                 if(s < 0)
737                 {
738                         //print(sprintf("MAY CEMENT AT: %d %d %d\n", x, y, rot));
739                         // moving down did not work - that means we can fixate the block here
740                         t1 = gettime(GETTIME_HIRES);
741
742                         CementPiece(pc, x, y, rot);
743                         s = BastetEvaluate();
744                         ClearPiece(pc, x, y, rot);
745
746                         t2 = gettime(GETTIME_HIRES);
747                         bastet_profile_evaluate_time += (t2 - t1);
748
749                         if(s > sm) sm = s;
750                 }
751         }
752         else
753         {
754                 t2 = gettime(GETTIME_HIRES);
755                 bastet_profile_checkmetrics_time += (t2 - t1);
756                 sm = -1; // impassible
757         }
758
759         bufstr_set(buf, b, ftos(sm));
760
761         return sm;
762 }
763
764 float bastet_piece[7];
765 float bastet_score[7];
766 float bastet_piecetime[7];
767 float BastetPiece()
768 {
769         float b;
770
771         bastet_profile_evaluate_time = 0;
772         bastet_profile_checkmetrics_time = 0;
773         var float t1 = gettime(GETTIME_HIRES);
774
775         b = buf_create(); bastet_piece[0] = 1; bastet_score[0] = BastetSearch(b, 1, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[0]; buf_del(b);
776         b = buf_create(); bastet_piece[1] = 2; bastet_score[1] = BastetSearch(b, 2, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[1]; buf_del(b);
777         b = buf_create(); bastet_piece[2] = 3; bastet_score[2] = BastetSearch(b, 3, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[2]; buf_del(b);
778         b = buf_create(); bastet_piece[3] = 4; bastet_score[3] = BastetSearch(b, 4, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[3]; buf_del(b);
779         b = buf_create(); bastet_piece[4] = 5; bastet_score[4] = BastetSearch(b, 5, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[4]; buf_del(b);
780         b = buf_create(); bastet_piece[5] = 6; bastet_score[5] = BastetSearch(b, 6, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[5]; buf_del(b);
781         b = buf_create(); bastet_piece[6] = 7; bastet_score[6] = BastetSearch(b, 7, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[6]; buf_del(b);
782
783         var float t2 = gettime(GETTIME_HIRES);
784         dprint(sprintf("Time taken: %.6f seconds (of this, ev = %.2f%%, cm = %.2f%%)\n", t2 - t1, 100 * bastet_profile_evaluate_time / (t2 - t1), 100 * bastet_profile_checkmetrics_time / (t2 - t1)));
785
786         // sort
787         float i, j, k, p, s;
788
789 /*
790         for(i = 0; i < 7; ++i)
791         {
792                 print(sprintf("piece %s value = %d\n", substring("OJLIZST", bastet_piece[i]-1, 1), bastet_score[i]));
793         }
794 */
795
796         for(i = 0; i < 7; ++i)
797         {
798                 k = i;
799                 p = bastet_piece[k];
800                 s = bastet_score[k];
801                 for(j = i + 1; j < 7; ++j)
802                 {
803                         if(bastet_score[j] < s)
804                         {
805                                 k = j;
806                                 s = bastet_score[k];
807                                 p = bastet_piece[k];
808                         }
809                 }
810                 if(k != i)
811                 {
812                         bastet_score[k] = bastet_score[i];
813                         bastet_piece[k] = bastet_piece[i];
814                         bastet_score[i] = s;
815                         bastet_piece[i] = p;
816                 }
817         }
818
819         b = random();
820         if(b < 0.8)
821                 j = 0;
822         else if(b < 0.92)
823                 j = 1;
824         else if(b < 0.98)
825                 j = 2;
826         else
827                 j = 3;
828         j = bastet_piece[j];
829
830         for(i = 0; i < 7; ++i)
831         {
832                 if(i == j-1)
833                         bastet_piecetime[i] = 0.2 * bastet_piecetime[i];
834                 else
835                         bastet_piecetime[i] = 1.8 * bastet_piecetime[i] + 1000;
836         }
837
838         return j;
839 }
840
841
842 /*
843 *********************************
844
845 Game Mechanics
846
847 *********************************
848 */
849 .float tet_piece_bucket;
850 float RandomPiece()
851 {
852         float i, j;
853         float p, q;
854         float b;
855         float seen;
856
857         if(self.tet_piece_bucket > 1)
858         {
859                 p = mod(self.tet_piece_bucket, 7);
860                 self.tet_piece_bucket = floor(self.tet_piece_bucket / 7);
861                 return p + 1;
862         }
863         else
864         {
865                 p = floor(random() * 7);
866                 seen = pow(2, p);
867                 b = 1;
868                 for(i = 6; i > 0; --i)
869                 {
870                         q = floor(random() * i);
871                         for(j = 0; j <= q; ++j)
872                                 if(seen & pow(2, j))
873                                         ++q;
874                         if(seen & pow(2, q))
875                                 error("foo 1");
876                         if(q >= 7)
877                                 error("foo 2");
878                         seen |= pow(2, q);
879                         b *= 7;
880                         b += q;
881                 }
882                 self.tet_piece_bucket = b;
883                 return p + 1;
884         }
885 };
886
887 void TetAddScore(float n)
888 {
889         self.tet_score = self.tet_score + n * Tetris_Level();
890         if (self.tet_score > tet_high_score)
891                 tet_high_score = self.tet_score;
892 };
893 float CheckMetrics(float piece, float orgx, float orgy, float rot) /*FIXDECL*/
894 {
895         // check to see if the piece, if moved to the locations will overlap
896
897         float x, y, l;
898         // why did I start counting from 1, damnit
899         orgx = orgx - 1;
900         orgy = orgy - 1;
901
902         PieceMinsMaxs(rot, piece);
903         for (y = tet_piecemins_y; y <= tet_piecemaxs_y; y = y + 1)
904         {
905                 l = GetLine(y + orgy);
906                 for (x = tet_piecemins_x; x <= tet_piecemaxs_x; x = x + 1)
907                 {
908                         if (PieceMetric(x, y, rot, piece))
909                         {
910                                 if (GetXBlock(x + orgx, l))
911                                         return FALSE; // uhoh, gonna hit something.
912                                 if (x+orgx<1 || x+orgx > TET_WIDTH || y+orgy<1 || y+orgy> TET_LINES)
913                                         return FALSE; // ouside the level
914                         }
915                 }
916         }
917         return TRUE;
918 }
919
920 void ClearPiece(float piece, float orgx, float orgy, float rot) /*FIXDECL*/
921 {
922         float x, y;
923         // why did I start counting from 1, damnit
924         orgx = orgx - 1;
925         orgy = orgy - 1;
926
927         PieceMinsMaxs(rot, piece);
928         for (y = tet_piecemins_y; y <= tet_piecemaxs_y; y = y + 1)
929         {
930                 for (x = tet_piecemins_x; x <= tet_piecemaxs_x; x = x + 1)
931                 {
932                         if (PieceMetric(x, y, rot, piece))
933                         {
934                                 SetSquare(x + orgx, y + orgy, 0);
935                         }
936                 }
937         }
938 }
939 void CementPiece(float piece, float orgx, float orgy, float rot) /*FIXDECL*/
940 {
941         float pcolor;
942         float x, y;
943         // why did I start counting from 1, damnit
944         orgx = orgx - 1;
945         orgy = orgy - 1;
946
947         pcolor = mod(piece, 3) + 1;
948
949         PieceMinsMaxs(rot, piece);
950         for (y = tet_piecemins_y; y <= tet_piecemaxs_y; y = y + 1)
951         {
952                 for (x = tet_piecemins_x; x <= tet_piecemaxs_x; x = x + 1)
953                 {
954                         if (PieceMetric(x, y, rot, piece))
955                         {
956                                 SetSquare(x + orgx, y + orgy, pcolor);
957                         }
958                 }
959         }
960 }
961
962 float LINE_LOW = 349525;
963 float LINE_HIGH = 699050; // above number times 2
964
965 void AddLines(float n)
966 {
967         entity head;
968         if(!self.tet_vs_id)
969                 return;
970         FOR_EACH_REALCLIENT(head) if(head != self) if(head.tetris_on) if(head.tet_vs_id == self.tet_vs_id)
971                 head.tet_vs_addlines += n;
972 }
973
974 void CompletedLines()
975 {
976         float y, cleared, ln, added, pos, i;
977
978         cleared = 0;
979         y = TET_LINES;
980         while(y >= 1)
981         {
982                 ln = GetLine(y);
983                 if (((ln & LINE_LOW) | ((ln & LINE_HIGH)/2)) == LINE_LOW)
984                         cleared = cleared + 1;
985                 else
986                         y = y - 1;
987                 ln = GetLine(y - cleared);
988                 SetLine(y, ln);
989         }
990
991         if(cleared >= 4)
992                 AddLines(cleared);
993         else if(cleared >= 1)
994                 AddLines(cleared - 1);
995
996         self.tet_lines = self.tet_lines + cleared;
997         TetAddScore(cleared * cleared * 10);
998
999         added = self.tet_vs_addlines;
1000         self.tet_vs_addlines = 0;
1001
1002         if(added)
1003         {
1004                 for(y = 1; y <= TET_LINES - added; ++y)
1005                 {
1006                         SetLine(y, GetLine(y + added));
1007                 }
1008                 for(y = max(1, TET_LINES - added + 1); y <= TET_LINES; ++y)
1009                 {
1010                         pos = floor(random() * TET_WIDTH);
1011                         ln = 0;
1012                         for(i = 1; i <= TET_WIDTH; ++i)
1013                                 if(i != pos)
1014                                         ln = SetXBlock(i, ln, floor(random() * 3 + 1));
1015                         SetLine(y, ln);
1016                 }
1017         }
1018
1019         self.tet_highest_line = 0;
1020         for(y = 1; y <= TET_LINES; ++y)
1021                 if(GetLine(y) != 0)
1022                 {
1023                         self.tet_highest_line = TET_LINES + 1 - y;
1024                         break;
1025                 }
1026
1027         if(added)
1028                 tetsnd("tetadd");
1029         else if(cleared >= 4)
1030                 tetsnd("tetris");
1031         else if(cleared)
1032                 tetsnd("tetline");
1033         else
1034                 tetsnd("tetland");
1035 };
1036
1037 void HandleGame(float keyss)
1038 {
1039
1040         // first off, we need to see if we need a new piece
1041         vector piece_data;
1042         vector check_pos;
1043         vector old_pos;
1044         float brand_new;
1045         float i;
1046         brand_new = 0;
1047
1048
1049         if (self.piece_type == 0)
1050         {
1051                 self.piece_pos = TET_START_PIECE_POS; // that's about middle top, we count from 1 ARGH
1052
1053                 if(autocvar_g_bastet)
1054                 {
1055                         self.piece_type = BastetPiece();
1056                         self.next_piece = bastet_piece[6];
1057                 }
1058                 else
1059                 {
1060                         if (self.next_piece)
1061                                 self.piece_type = self.next_piece;
1062                         else
1063                                 self.piece_type = RandomPiece();
1064                         self.next_piece =  RandomPiece();
1065                 }
1066                 keyss = 0; // no movement first frame
1067                 self.tet_autodown = time + 0.2;
1068                 brand_new = 1;
1069         }
1070         else
1071                 ClearPiece(self.piece_type, self.piece_pos_x, self.piece_pos_y, self.piece_pos_z);
1072
1073         // next we need to check the piece metrics against what's on the level
1074         // based on the key order
1075
1076         old_pos = check_pos = self.piece_pos;
1077
1078         float nudge;
1079         nudge = 0;
1080         if (keyss & TETKEY_RIGHT)
1081         {
1082                 check_pos_x = check_pos_x + 1;
1083                 tetsnd("tetmove");
1084         }
1085         else if (keyss & TETKEY_LEFT)
1086         {
1087                 check_pos_x = check_pos_x - 1;
1088                 tetsnd("tetmove");
1089         }
1090         else if (keyss & TETKEY_ROTRIGHT)
1091         {
1092                 check_pos_z = check_pos_z + 1;
1093                 piece_data = PieceShape(self.piece_type);
1094                 nudge = 1;
1095                 tetsnd("tetrot");
1096         }
1097         else if (keyss & TETKEY_ROTLEFT)
1098         {
1099                 check_pos_z = check_pos_z - 1;
1100                 piece_data = PieceShape(self.piece_type);
1101                 nudge = 1;
1102                 tetsnd("tetrot");
1103         }
1104         // bounds check
1105         if (check_pos_z > 3)
1106                 check_pos_z = 0;
1107         else if (check_pos_z < 0)
1108                 check_pos_z = 3;
1109
1110         // reality check
1111         if (CheckMetrics(self.piece_type, check_pos_x, check_pos_y, check_pos_z))
1112                 self.piece_pos = check_pos;
1113         else if (brand_new)
1114         {
1115                 self.tetris_on = 2;
1116                 self.tet_gameovertime = time + 5;
1117                 return;
1118         }
1119         else
1120         {
1121                 for(i = 1; i <= nudge; ++i)
1122                 {
1123                         if(CheckMetrics(self.piece_type, check_pos_x + i, check_pos_y, check_pos_z))
1124                                 self.piece_pos = check_pos + '1 0 0' * i;
1125                         else if(CheckMetrics(self.piece_type, check_pos_x - i, check_pos_y, check_pos_z))
1126                                 self.piece_pos = check_pos - '1 0 0' * i;
1127                         else
1128                                 continue;
1129                         break;
1130                 }
1131         }
1132         check_pos = self.piece_pos;
1133         if(keyss & TETKEY_DROP)
1134         {
1135                 // drop to bottom, but do NOT cement it yet
1136                 // this allows sliding it
1137                 ++check_pos_y;
1138                 while(CheckMetrics(self.piece_type, check_pos_x, check_pos_y + 1, check_pos_z))
1139                         ++check_pos_y;
1140                 self.tet_autodown = time + 2 / (1 + Tetris_Level());
1141         }
1142         else if (keyss & TETKEY_DOWN)
1143         {
1144                 check_pos_y = check_pos_y + 1;
1145                 self.tet_autodown = time + 2 / (1 + Tetris_Level());
1146         }
1147         else if (self.tet_autodown < time)
1148         {
1149                 check_pos_y = check_pos_y + 1;
1150                 self.tet_autodown = time + 2 / (1 + Tetris_Level());
1151         }
1152         if (CheckMetrics(self.piece_type, check_pos_x, check_pos_y, check_pos_z))
1153         {
1154                 if(old_pos != check_pos)
1155                         self.tet_drawtime = 0;
1156                 self.piece_pos = check_pos;
1157         }
1158         else
1159         {
1160                 CementPiece(self.piece_type, self.piece_pos_x, self.piece_pos_y, self.piece_pos_z);
1161                 TetAddScore(1);
1162                 CompletedLines();
1163                 self.piece_type = 0;
1164                 self.tet_drawtime = 0;
1165                 return;
1166         }
1167         CementPiece(self.piece_type, self.piece_pos_x, self.piece_pos_y, self.piece_pos_z);
1168 };
1169
1170 /*
1171 *********************************
1172
1173 Important Linking Into Quake stuff
1174
1175 *********************************
1176 */
1177
1178
1179 void TetrisImpulse()
1180 {
1181         if(self.tetris_on == 0 || self.tetris_on == 2) // from "off" or "game over"
1182         {
1183                 self.tetris_on = 3;
1184
1185                 if(time < tet_vs_current_timeout)
1186                 {
1187                         // join VS game
1188                         self.tet_vs_id = tet_vs_current_id;
1189                 }
1190                 else
1191                 {
1192                         // start new VS game
1193                         ++tet_vs_current_id;
1194                         tet_vs_current_timeout = time + 15;
1195                         self.tet_vs_id = tet_vs_current_id;
1196                         bprint("^1TET^7R^1IS: ", self.netname, "^1 started a new game. Do ^7impulse 100^1 to join.\n");
1197                 }
1198                 self.tet_highest_line = 0;
1199                 ResetTetris();
1200                 self.tet_org = self.origin;
1201                 self.movetype = MOVETYPE_NOCLIP;
1202         }
1203         else if(self.tetris_on == 1) // from "on"
1204         {
1205                 Tet_GameExit();
1206                 self.impulse = 0;
1207         }
1208 };
1209
1210
1211 float TetrisPreFrame()
1212 {
1213         if (!self.tetris_on)
1214                 return 0;
1215
1216         self.tet_org = self.origin;
1217         if (self.tet_drawtime > time)
1218                 return 1;
1219         Draw_Tetris();
1220         if(self.tetris_on == 3)
1221                 self.tet_drawtime = ceil(time - tet_vs_current_timeout + 0.1) + tet_vs_current_timeout;
1222         else
1223                 self.tet_drawtime = time + 0.5;
1224         return 1;
1225 };
1226 float frik_anglemoda(float v)
1227 {
1228         return v - floor(v/360) * 360;
1229 };
1230 float angcompa(float y1, float y2)
1231 {
1232         y1 = frik_anglemoda(y1);
1233         y2 = frik_anglemoda(y2);
1234
1235         local float answer;
1236         answer = y1 - y2;
1237         if (answer > 180)
1238                 answer = answer - 360;
1239         else if (answer < -180)
1240                 answer = answer + 360;
1241         return answer;
1242 };
1243
1244 .float tetkey_down, tetkey_rotright, tetkey_left, tetkey_right, tetkey_rotleft, tetkey_drop;
1245
1246 float TetrisKeyRepeat(.float fld, float f)
1247 {
1248         if(f)
1249         {
1250                 if(self.fld == 0) // initial key press
1251                 {
1252                         self.fld = time + 0.3;
1253                         return 1;
1254                 }
1255                 else if(time > self.fld)
1256                 {
1257                         self.fld = time + 0.1;
1258                         return 1;
1259                 }
1260                 else
1261                 {
1262                         // repeating too fast
1263                         return 0;
1264                 }
1265         }
1266         else
1267         {
1268                 self.fld = 0;
1269                 return 0;
1270         }
1271 }
1272
1273 float TetrisPostFrame()
1274 {
1275         float keysa;
1276
1277         keysa = 0;
1278
1279         if (!self.tetris_on)
1280                 return 0;
1281
1282         if(self.tetris_on == 2 && time > self.tet_gameovertime)
1283         {
1284                 Tet_GameExit();
1285                 return 0;
1286         }
1287
1288         if(self.tetris_on == 3 && time > tet_vs_current_timeout)
1289         {
1290                 self.tetris_on = 1; // start VS game
1291                 self.tet_drawtime = 0;
1292         }
1293
1294         setorigin(self, self.tet_org);
1295         self.movetype = MOVETYPE_NONE;
1296
1297         if(self.tetris_on == 1)
1298         {
1299                 if(TetrisKeyRepeat(tetkey_down, self.movement_x < 0))
1300                         keysa |= TETKEY_DOWN;
1301
1302                 if(TetrisKeyRepeat(tetkey_rotright, self.movement_x > 0))
1303                         keysa |= TETKEY_ROTRIGHT;
1304
1305                 if(TetrisKeyRepeat(tetkey_left, self.movement_y < 0))
1306                         keysa |= TETKEY_LEFT;
1307
1308                 if(TetrisKeyRepeat(tetkey_right, self.movement_y > 0))
1309                         keysa |= TETKEY_RIGHT;
1310
1311                 if(TetrisKeyRepeat(tetkey_rotleft, self.BUTTON_CROUCH))
1312                         keysa |= TETKEY_ROTLEFT;
1313
1314                 if(TetrisKeyRepeat(tetkey_drop, self.BUTTON_JUMP))
1315                         keysa |= TETKEY_DROP;
1316
1317                 HandleGame(keysa);
1318         }
1319
1320         return 1;
1321 };
1322
1323 #endif