1 /* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
2 * This program is public domain.
16 /*************************************************************************/
18 int piecefreq
[7]; /* Frequency (percentage) for each block type */
19 int specialfreq
[9]; /* Frequency for each special type */
20 int old_mode
; /* Old mode? (i.e. Gameboy-style) */
21 int initial_level
; /* Initial level */
22 int lines_per_level
; /* Number of lines per level-up */
23 int level_inc
; /* Levels to increase at each level-up */
24 int level_average
; /* Average all players' levels */
25 int special_lines
; /* Number of lines needed for a special block */
26 int special_count
; /* Number of special blocks added each time */
27 int special_capacity
; /* Capacity of special block inventory */
29 Field fields
[6]; /* Current field states */
30 int levels
[6]; /* Current levels */
31 int lines
; /* Lines completed (by us) */
32 char specials
[MAX_SPECIALS
] = {-1}; /* Special block inventory */
33 int next_piece
; /* Next piece to fall */
35 static struct timeval timeout
; /* Time of next action */
36 int current_piece
; /* Current piece number */
37 int current_rotation
; /* Current rotation value */
38 int current_x
; /* Current X position */
39 int current_y
; /* Current Y position */
40 static int piece_waiting
; /* Are we waiting for a new piece to start? */
42 static int last_special
; /* Last line for which we added a special */
44 /*************************************************************************/
48 /*************************************************************************/
50 /* The array of piece shapes. It is organized as:
53 * - 4 rotations (ordered clockwise)
55 * A . is an empty point, a # is a full one. An X (upper-case) represents
56 * the "hot-spot" of the piece; this is where the coordinates are fastened
57 * to, and is used to determine the piece's new position after rotation.
58 * If the location for an X empty, use a lowercase letter instead.
60 * This is all parsed by init_shapes, which should be called at startup.
63 static const char shapes
[7][4][4][4] = {
64 { { "##X#", "..X.", "##X#", "..X." },
65 { "....", "..#.", "....", "..#." },
66 { "....", "..#.", "....", "..#." },
67 { "....", "..#.", "....", "..#." } },
69 { { "....", "....", "....", "...." },
70 { ".X#.", ".X#.", ".X#.", ".X#." },
71 { ".##.", ".##.", ".##.", ".##." },
72 { "....", "....", "....", "...." } },
74 { { "....", ".#..", "#...", ".##." },
75 { "#X#.", ".X..", "#X#.", ".X.." },
76 { "..#.", "##..", "....", ".#.." },
77 { "....", "....", "....", "...." } },
79 { { "....", "##..", "..#.", ".#.." },
80 { "#X#.", ".X..", "#X#.", ".X.." },
81 { "#...", ".#..", "....", ".##." },
82 { "....", "....", "....", "...." } },
84 { { "....", ".#..", "....", ".#.." },
85 { "#X..", "#X..", "#X..", "#X.." },
86 { ".##.", "#...", ".##.", "#..." },
87 { "....", "....", "....", "...." } },
89 { { "....", "#...", "....", "#..." },
90 { ".X#.", "#X..", ".X#.", "#X.." },
91 { "##..", ".#..", "##..", ".#.." },
92 { "....", "....", "....", "...." } },
94 { { "....", ".#..", ".#..", ".#.." },
95 { "#X#.", "#X..", "#X#.", ".X#." },
96 { ".#..", ".#..", "....", ".#.." },
97 { "....", "....", "....", "...." } }
100 /* piecedata[piece][rot]; filled in by init_shapes() */
101 PieceData piecedata
[7][4];
103 /*************************************************************************/
105 /* Parse the shapes array and fill in the piece data. */
107 void init_shapes(void)
111 for (i
= 0; i
< 7; i
++) {
112 for (r
= 0; r
< 4; r
++) {
113 piecedata
[i
][r
].hot_x
= -1;
114 piecedata
[i
][r
].hot_y
= -1;
115 piecedata
[i
][r
].top
= 3;
116 piecedata
[i
][r
].left
= 3;
117 piecedata
[i
][r
].bottom
= 0;
118 piecedata
[i
][r
].right
= 0;
119 for (y
= 0; y
< 4; y
++) {
120 for (x
= 0; x
< 4; x
++) {
121 switch (shapes
[i
][y
][r
][x
]) {
123 piecedata
[i
][r
].shape
[y
][x
] = 0;
126 piecedata
[i
][r
].shape
[y
][x
] = 1;
127 if (piecedata
[i
][r
].top
> y
)
128 piecedata
[i
][r
].top
= y
;
129 if (piecedata
[i
][r
].left
> x
)
130 piecedata
[i
][r
].left
= x
;
131 if (piecedata
[i
][r
].bottom
< y
)
132 piecedata
[i
][r
].bottom
= y
;
133 if (piecedata
[i
][r
].right
< x
)
134 piecedata
[i
][r
].right
= x
;
137 piecedata
[i
][r
].shape
[y
][x
] = 0;
138 piecedata
[i
][r
].hot_x
= x
;
139 piecedata
[i
][r
].hot_y
= y
;
142 piecedata
[i
][r
].shape
[y
][x
] = 1;
143 if (piecedata
[i
][r
].top
> y
)
144 piecedata
[i
][r
].top
= y
;
145 if (piecedata
[i
][r
].left
> x
)
146 piecedata
[i
][r
].left
= x
;
147 if (piecedata
[i
][r
].bottom
< y
)
148 piecedata
[i
][r
].bottom
= y
;
149 if (piecedata
[i
][r
].right
< x
)
150 piecedata
[i
][r
].right
= x
;
151 piecedata
[i
][r
].hot_x
= x
;
152 piecedata
[i
][r
].hot_y
= y
;
155 fprintf(stderr
, "Piece %d rotation %d: "
156 "weird character `%c' at (%d,%d)\n",
157 i
, r
, shapes
[i
][y
][r
][x
], x
, y
);
162 if (piecedata
[i
][r
].hot_x
< 0 || piecedata
[i
][r
].hot_y
< 0) {
163 fprintf(stderr
, "Piece %d rotation %d missing hot spot!\n",
171 /*************************************************************************/
173 /* Retrieve the shape for the given piece and rotation. Return -1 if piece
174 * or rotation is invalid, else 0.
177 int get_shape(int piece
, int rotation
, char buf
[4][4])
182 if (piece
< 0 || piece
> 6 || rotation
< 0 || rotation
> 3)
184 shape
= (char *) piecedata
[piece
][rotation
].shape
;
185 for (y
= 0; y
< 4; y
++) {
186 for (x
= 0; x
< 4; x
++) {
187 buf
[y
][x
] = *shape
++ ? piece
%5 + 1 : 0;
193 /*************************************************************************/
194 /*************************************************************************/
196 /* Return the number of milliseconds of delay between piece drops for the
200 static int level_delay()
202 int level
= levels
[my_playernum
-1];
206 delay
= (delay
*69+35)/70; /* multiply by 69/70 and round */
210 /*************************************************************************/
212 /* Return whether the piece in the position given by the x, y, and rot
213 * variables (interpreted the same way as current_*) would overlap any
214 * other blocks in the field. A value of -1 means use the current_* value.
217 static int piece_overlaps(int x
, int y
, int rot
)
219 Field
*f
= &fields
[my_playernum
-1];
228 rot
= current_rotation
;
229 pd
= &piecedata
[current_piece
][rot
];
233 for (j
= 0; ok
&& j
< 4; j
++) {
236 for (i
= 0; ok
&& i
< 4; i
++) {
237 if (pd
->shape
[j
][i
] && (y
+j
>= FIELD_HEIGHT
|| x
+i
< 0
238 || x
+i
>= FIELD_WIDTH
|| (*f
)[y
+j
][x
+i
]))
245 /*************************************************************************/
247 /* Draw the piece in its current position on the board. If draw == 0, then
248 * erase the piece rather than drawing it.
251 static void draw_piece(int draw
)
253 Field
*f
= &fields
[my_playernum
-1];
254 char c
= draw
? current_piece
% 5 + 1 : 0;
255 int x
= current_x
- piecedata
[current_piece
][current_rotation
].hot_x
;
256 int y
= current_y
- piecedata
[current_piece
][current_rotation
].hot_y
;
257 char *shape
= (char *) piecedata
[current_piece
][current_rotation
].shape
;
260 for (j
= 0; j
< 4; j
++) {
265 for (i
= 0; i
< 4; i
++) {
272 /*************************************************************************/
274 /* Clear any full lines on the field; return the number of lines cleared. */
276 static int clear_lines(int add_specials
)
278 Field
*f
= &fields
[my_playernum
-1];
279 int x
, y
, count
= 0, i
, j
, k
;
282 for (y
= 0; y
< FIELD_HEIGHT
; y
++) {
284 for (x
= 0; x
< FIELD_WIDTH
; x
++) {
285 if ((*f
)[y
][x
] == 0) {
294 memset(new_specials
, 0, sizeof(new_specials
));
295 for (y
= 0; y
< FIELD_HEIGHT
; y
++) {
297 for (x
= 0; x
< FIELD_WIDTH
; x
++) {
298 if ((*f
)[y
][x
] == 0) {
304 for (x
= 0; x
< FIELD_WIDTH
; x
++) {
306 new_specials
[(*f
)[y
][x
]-6]++;
309 memmove((*f
)[1], (*f
)[0], FIELD_WIDTH
*y
);
310 memset((*f
)[0], 0, FIELD_WIDTH
);
316 while (pos
< special_capacity
&& specials
[pos
] >= 0)
318 for (i
= 0; i
< count
&& pos
< special_capacity
; i
++) {
319 for (j
= 0; j
< 9 && pos
< special_capacity
; j
++) {
320 for (k
= 0; k
< new_specials
[j
] && pos
< special_capacity
; k
++){
321 if (windows_mode
&& rand()%2) {
322 memmove(specials
+1, specials
, pos
);
330 if (pos
< special_capacity
)
338 /*************************************************************************/
340 /* Place the given number of specials on the field. If there aren't enough
341 * blocks to replace, replace all of the blocks and drop the rest of the
345 static void place_specials(int num
)
347 Field
*f
= &fields
[my_playernum
-1];
348 int nblocks
= 0, left
;
351 for (y
= 0; y
< FIELD_HEIGHT
; y
++) {
352 for (x
= 0; x
< FIELD_WIDTH
; x
++) {
361 while (left
> 0 && tries
> 0) {
362 for (y
= 0; left
> 0 && y
< FIELD_HEIGHT
; y
++) {
363 for (x
= 0; left
> 0 && x
< FIELD_WIDTH
; x
++) {
364 if ((*f
)[y
][x
] > 5 || (*f
)[y
][x
] == 0)
366 if (rand() % nblocks
< num
) {
367 int which
= 0, n
= rand() % 100;
368 while (n
>= specialfreq
[which
]) {
369 n
-= specialfreq
[which
];
372 (*f
)[y
][x
] = 6 + which
;
381 /*************************************************************************/
383 /* Send the new field, either as differences from the given old field or
384 * (if more efficient) as a complete field. If oldfield is NULL, always
385 * send the complete field.
388 static void send_field(Field
*oldfield
)
390 Field
*f
= &fields
[my_playernum
-1];
391 int i
, x
, y
, diff
= 0;
395 for (y
= 0; y
< FIELD_HEIGHT
; y
++) {
396 for (x
= 0; x
< FIELD_WIDTH
; x
++) {
397 if ((*f
)[y
][x
] != (*oldfield
)[y
][x
])
402 diff
= FIELD_WIDTH
* FIELD_HEIGHT
;
404 if (diff
< (FIELD_WIDTH
*FIELD_HEIGHT
)/2) {
405 s
= buf
+ sprintf(buf
, "f %d ", my_playernum
);
406 for (i
= 0; i
< 15; i
++) {
407 int seen
= 0; /* Have we seen a difference of this block? */
408 for (y
= 0; y
< FIELD_HEIGHT
; y
++) {
409 for (x
= 0; x
< FIELD_WIDTH
; x
++) {
410 if ((*f
)[y
][x
] == i
&& (*f
)[y
][x
] != (*oldfield
)[y
][x
]) {
420 } /* for i (each tile type) */
421 } /* difference check */
422 /* -4 below is to adjust for "f %d " */
423 if (diff
>= (FIELD_WIDTH
*FIELD_HEIGHT
)/2
424 || strlen(buf
)-4 > FIELD_WIDTH
*FIELD_HEIGHT
) {
425 static const char specials
[] = "acnrsbgqo";
426 s
= buf
+ sprintf(buf
, "f %d ", my_playernum
);
427 for (y
= 0; y
< FIELD_HEIGHT
; y
++) {
428 for (x
= 0; x
< FIELD_WIDTH
; x
++) {
430 *s
++ = specials
[(*f
)[y
][x
]-6];
432 *s
++ = (*f
)[y
][x
] + '0';
437 sputs(buf
, server_sock
);
440 /*************************************************************************/
441 /*************************************************************************/
443 /* Generate a new piece and set up the timer. */
450 current_piece
= next_piece
;
453 while (n
>= piecefreq
[next_piece
] && next_piece
< 6) {
454 n
-= piecefreq
[next_piece
];
457 current_rotation
= 0;
458 pd
= &piecedata
[current_piece
][current_rotation
];
460 current_y
= pd
->hot_y
- pd
->top
;
461 if (piece_overlaps(-1, -1, -1)) {
463 if (piece_overlaps(-1, -1, -1)) {
465 if (piece_overlaps(-1, -1, -1)) {
466 Field
*f
= &fields
[my_playernum
-1];
468 for (y
= 0; y
< FIELD_HEIGHT
; y
++) {
469 for (x
= 0; x
< FIELD_WIDTH
; x
++)
470 (*f
)[y
][x
] = rand()%5 + 1;
473 sockprintf(server_sock
, "playerlost %d", my_playernum
);
475 not_playing_game
= 1;
481 io
->draw_own_field();
482 gettimeofday(&timeout
, NULL
);
483 timeout
.tv_usec
+= level_delay() * 1000;
484 timeout
.tv_sec
+= timeout
.tv_usec
/ 1000000;
485 timeout
.tv_usec
%= 1000000;
489 /*************************************************************************/
491 /* Step the current piece down one space. If it's already as far as it can
492 * go, solidify it, check for completed lines, send the new field state,
493 * and start a new piece.
498 Field
*f
= &fields
[my_playernum
-1];
499 PieceData
*pd
= &piecedata
[current_piece
][current_rotation
];
500 int y
= current_y
- pd
->hot_y
;
505 if (y
+1 + pd
->bottom
< FIELD_HEIGHT
&& !piece_overlaps(-1, ynew
, -1)) {
508 io
->draw_own_field();
509 gettimeofday(&timeout
, NULL
);
510 timeout
.tv_usec
+= level_delay() * 1000;
511 timeout
.tv_sec
+= timeout
.tv_usec
/ 1000000;
512 timeout
.tv_usec
%= 1000000;
514 int completed
, level
, nspecials
;
518 memcpy(&oldfield
, f
, sizeof(oldfield
));
520 if (last_special
> lines
) /* i.e. from a previous game */
522 completed
= clear_lines(1);
524 if (old_mode
&& completed
> 1) {
527 sockprintf(server_sock
, "sb 0 cs%d %d", completed
, my_playernum
);
528 sprintf(buf
, "cs%d", completed
);
529 io
->draw_attdef(buf
, my_playernum
, 0);
531 level
= initial_level
+ (lines
/ lines_per_level
) * level_inc
;
534 levels
[my_playernum
] = level
;
536 sockprintf(server_sock
, "lvl %d %d", my_playernum
, level
);
539 nspecials
= (lines
- last_special
) / special_lines
;
540 last_special
+= nspecials
* special_lines
;
541 nspecials
*= special_count
;
542 place_specials(nspecials
);
543 io
->draw_own_field();
544 send_field(&oldfield
);
546 gettimeofday(&timeout
, NULL
);
547 timeout
.tv_usec
+= tetrifast
? 0 : 600000;
548 timeout
.tv_sec
+= timeout
.tv_usec
/ 1000000;
549 timeout
.tv_usec
%= 1000000;
553 /*************************************************************************/
555 /* Do something for a special block. */
557 void do_special(const char *type
, int from
, int to
)
559 Field
*f
= &fields
[my_playernum
-1];
563 io
->draw_attdef(type
, from
, to
);
567 if (to
!= 0 && to
!= my_playernum
&& !(from
==my_playernum
&& *type
=='s'))
573 memcpy(&oldfield
, f
, sizeof(Field
));
575 if (strncmp(type
, "cs", 2) == 0) {
576 int nlines
= atoi(type
+2);
578 /* Don't add lines from a team member */
579 if (!teams
[my_playernum
-1]
581 || strcmp(teams
[my_playernum
-1],teams
[from
-1]) != 0
584 memmove((*f
)[0], (*f
)[1], FIELD_WIDTH
*(FIELD_HEIGHT
-1));
585 for (x
= 0; x
< FIELD_WIDTH
; x
++)
586 (*f
)[21][x
] = 1 + rand()%5;
587 (*f
)[FIELD_HEIGHT
-1][rand()%FIELD_WIDTH
] = 0;
591 } else if (*type
== 'a') {
592 memmove((*f
)[0], (*f
)[1], FIELD_WIDTH
*(FIELD_HEIGHT
-1));
593 for (x
= 0; x
< FIELD_WIDTH
; x
++)
594 (*f
)[21][x
] = 1 + rand()%5;
595 (*f
)[FIELD_HEIGHT
-1][rand()%FIELD_WIDTH
] = 0;
596 (*f
)[FIELD_HEIGHT
-1][rand()%FIELD_WIDTH
] = 0;
597 (*f
)[FIELD_HEIGHT
-1][rand()%FIELD_WIDTH
] = 0;
599 } else if (*type
== 'b') {
600 for (y
= 0; y
< FIELD_HEIGHT
; y
++) {
601 for (x
= 0; x
< FIELD_WIDTH
; x
++) {
603 (*f
)[y
][x
] = rand()%5 + 1;
607 } else if (*type
== 'c') {
608 memmove((*f
)[1], (*f
)[0], FIELD_WIDTH
*(FIELD_HEIGHT
-1));
609 memset((*f
)[0], 0, FIELD_WIDTH
);
611 } else if (*type
== 'g') {
612 for (x
= 0; x
< FIELD_WIDTH
; x
++) {
615 if ((*f
)[y
][x
] == 0) {
616 int y2
, allclear
= 1;
617 for (y2
= y
-1; allclear
&& y2
>= 0; y2
--) {
623 for (y2
= y
-1; y2
>= 0; y2
--)
624 (*f
)[y2
+1][x
] = (*f
)[y2
][x
];
632 } else if (*type
== 'n') {
633 memset(*f
, 0, FIELD_WIDTH
*FIELD_HEIGHT
);
635 } else if (*type
== 'o') {
636 int tries
, x2
, y2
, xnew
, ynew
;
638 for (y
= 0; y
< FIELD_HEIGHT
; y
++) {
639 for (x
= 0; x
< FIELD_WIDTH
; x
++) {
640 if ((*f
)[y
][x
] != 6 + SPECIAL_O
)
643 for (y2
= y
-1; y2
<= y
+1; y2
++) {
644 if (y2
< 0 || y2
>= FIELD_HEIGHT
)
646 for (x2
= x
-1; x2
<= x
+1; x2
++) {
647 if (x2
< 0 || x2
>= FIELD_WIDTH
)
649 if (!windows_mode
&& !(*f
)[y2
][x2
])
653 xnew
= random() % FIELD_WIDTH
;
654 ynew
= FIELD_HEIGHT
-1 - random()%16;
655 if (windows_mode
|| !(*f
)[ynew
][xnew
]) {
656 (*f
)[ynew
][xnew
] = (*f
)[y2
][x2
];
667 } else if (*type
== 'q') {
668 for (y
= 0; y
< FIELD_HEIGHT
; y
++) {
669 int r
= rand()%3 - 1;
671 int save
= (*f
)[y
][0];
672 memmove((*f
)[y
], (*f
)[y
]+1, FIELD_WIDTH
-1);
674 (*f
)[y
][FIELD_WIDTH
-1] = 0;
676 (*f
)[y
][FIELD_WIDTH
-1] = save
;
678 int save
= (*f
)[y
][FIELD_WIDTH
-1];
679 memmove((*f
)[y
]+1, (*f
)[y
], FIELD_WIDTH
-1);
687 } else if (*type
== 'r') {
690 for (i
= 0; i
< 10; i
++) {
691 x
= rand() % FIELD_WIDTH
;
692 y
= rand() % FIELD_HEIGHT
;
693 if ((*f
)[y
][x
] != 0) {
699 } else if (*type
== 's') {
702 memcpy(temp
, fields
[from
-1], sizeof(Field
));
703 memcpy(fields
[from
-1], fields
[to
-1], sizeof(Field
));
704 memcpy(fields
[to
-1], temp
, sizeof(Field
));
705 if (from
== my_playernum
|| to
== my_playernum
)
706 memset(fields
[my_playernum
-1], 0, 6*FIELD_WIDTH
);
707 if (from
!= my_playernum
)
708 io
->draw_other_field(from
);
709 if (to
!= my_playernum
)
710 io
->draw_other_field(to
);
714 send_field(&oldfield
);
716 if (!piece_waiting
) {
717 while (piece_overlaps(-1, -1, -1))
721 io
->draw_own_field();
724 /*************************************************************************/
725 /*************************************************************************/
727 /* Deal with the in-game message input buffer. */
729 static char gmsg_buffer
[512];
732 #define curpos (gmsg_buffer+gmsg_pos)
734 /*************************************************************************/
736 static void gmsg_input(int c
)
738 if (gmsg_pos
< sizeof(gmsg_buffer
) - 1) {
739 memmove(curpos
+1, curpos
, strlen(curpos
)+1);
740 gmsg_buffer
[gmsg_pos
++] = c
;
741 io
->draw_gmsg_input(gmsg_buffer
, gmsg_pos
);
745 /*************************************************************************/
747 static void gmsg_delete(void)
749 if (gmsg_buffer
[gmsg_pos
]) {
750 memmove(curpos
, curpos
+1, strlen(curpos
)-1+1);
751 io
->draw_gmsg_input(gmsg_buffer
, gmsg_pos
);
755 /*************************************************************************/
757 static void gmsg_backspace(void)
765 /*************************************************************************/
767 static void gmsg_kill(void)
771 io
->draw_gmsg_input(gmsg_buffer
, gmsg_pos
);
774 /*************************************************************************/
776 static void gmsg_move(int how
)
780 io
->draw_gmsg_input(gmsg_buffer
, gmsg_pos
);
781 } else if (how
== -1 && gmsg_pos
> 0) {
783 io
->draw_gmsg_input(gmsg_buffer
, gmsg_pos
);
784 } else if (how
== 1 && gmsg_buffer
[gmsg_pos
]) {
786 io
->draw_gmsg_input(gmsg_buffer
, gmsg_pos
);
787 } else if (how
== 2) {
788 gmsg_pos
= strlen(gmsg_buffer
);
789 io
->draw_gmsg_input(gmsg_buffer
, gmsg_pos
);
793 /*************************************************************************/
795 static void gmsg_enter(void)
798 if (strncasecmp(gmsg_buffer
, "/me ", 4) == 0)
799 sockprintf(server_sock
, "gmsg * %s %s", players
[my_playernum
-1], gmsg_buffer
+4);
801 sockprintf(server_sock
, "gmsg <%s> %s", players
[my_playernum
-1], gmsg_buffer
);
804 io
->clear_gmsg_input();
810 /*************************************************************************/
811 /*************************************************************************/
813 /* Set up for a new game. */
819 gettimeofday(&timeout
, NULL
);
820 timeout
.tv_usec
+= 1200000;
821 timeout
.tv_sec
+= timeout
.tv_usec
/ 1000000;
822 timeout
.tv_usec
%= 1000000;
826 while (n
>= piecefreq
[next_piece
] && next_piece
< 6) {
827 n
-= piecefreq
[next_piece
];
832 /*************************************************************************/
834 /* Return the number of milliseconds until we want to do something. */
836 int tetris_timeout(void)
841 gettimeofday(&tv
, NULL
);
842 t
= (timeout
.tv_sec
- tv
.tv_sec
) * 1000
843 + (timeout
.tv_usec
-tv
.tv_usec
) / 1000;
847 /*************************************************************************/
849 /* Do something when we hit a timeout. */
851 void tetris_timeout_action(void)
859 /*************************************************************************/
861 /* Do something with a character of input. */
863 static const char special_chars
[] = "acnrsbgqo";
865 void tetris_input(int c
)
867 PieceData
*pd
= &piecedata
[current_piece
][current_rotation
];
868 int x
= current_x
- pd
->hot_x
;
869 int y
= current_y
- pd
->hot_y
;
871 static int gmsg_active
= 0;
874 if (c
== 8 || c
== 127) /* Backspace or Delete */
876 else if (c
== 4) /* Ctrl-D */
878 else if (c
== 21) /* Ctrl-U */
880 else if (c
== K_LEFT
)
882 else if (c
== K_RIGHT
)
884 else if (c
== 1) /* Ctrl-A */
886 else if (c
== 5) /* Ctrl-E */
888 else if (c
== '\r' || c
== '\n') {
891 } else if (c
== 27) { /* Escape */
892 io
->clear_gmsg_input();
894 } else if (c
>= 1 && c
<= 0xFF)
899 if (c
!= 't' && (!playing_game
|| game_paused
))
903 case K_UP
: /* Rotate clockwise */
907 rnew
= (current_rotation
+1) % 4;
908 pd
= &piecedata
[current_piece
][current_rotation
];
909 x
= current_x
- pd
->hot_x
;
910 y
= current_y
- pd
->hot_y
;
911 if (x
+ pd
->left
< 0 || x
+ pd
->right
>= FIELD_WIDTH
912 || y
+ pd
->bottom
>= FIELD_HEIGHT
)
915 if (!piece_overlaps(-1, -1, rnew
)) {
916 current_rotation
= rnew
;
918 io
->draw_own_field();
924 case 'z': /* Rotate counterclockwise */
927 rnew
= (current_rotation
+3) % 4;
928 pd
= &piecedata
[current_piece
][current_rotation
];
929 x
= current_x
- pd
->hot_x
;
930 y
= current_y
- pd
->hot_y
;
931 if (x
+ pd
->left
< 0 || x
+ pd
->right
>= FIELD_WIDTH
932 || y
+ pd
->bottom
>= FIELD_HEIGHT
)
935 if (!piece_overlaps(-1, -1, rnew
)) {
936 current_rotation
= rnew
;
938 io
->draw_own_field();
944 case K_LEFT
: /* Move left */
947 if (x
+ pd
->left
> 0) {
949 if (!piece_overlaps(current_x
-1, -1, -1)) {
952 io
->draw_own_field();
959 case K_RIGHT
: /* Move right */
962 if (x
+ pd
->right
< FIELD_WIDTH
-1) {
964 if (!piece_overlaps(current_x
+1, -1, -1)) {
967 io
->draw_own_field();
974 case K_DOWN
: /* Down one space */
980 case ' ': /* Down until the piece hits something */
985 while (y
+ pd
->bottom
< FIELD_HEIGHT
&& !piece_overlaps(-1,ynew
,-1)) {
990 if (ynew
!= current_y
) {
993 current_y
++; /* Don't allow sliding */
1001 if (specials
[0] == -1)
1003 if (special_capacity
> 1)
1004 memmove(specials
, specials
+1, special_capacity
-1);
1005 specials
[special_capacity
-1] = -1;
1006 io
->draw_specials();
1020 if (specials
[0] == -1)
1022 sockprintf(server_sock
, "sb %d %c %d",
1023 c
, special_chars
[(int) specials
[0]], my_playernum
);
1024 buf
[0] = special_chars
[(int) specials
[0]];
1026 do_special(buf
, my_playernum
, c
);
1027 if (special_capacity
> 1)
1028 memmove(specials
, specials
+1, special_capacity
-1);
1029 specials
[special_capacity
-1] = -1;
1030 io
->draw_specials();
1036 io
->draw_gmsg_input(gmsg_buffer
, gmsg_pos
);
1042 /*************************************************************************/
1044 #endif /* !SERVER_ONLY */
1046 /*************************************************************************/