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