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