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