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