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