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