]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/minigames/minigame/ttt.qc
Fix FL_WEAPON flag overlapping FL_JUMPRELEASED. This unintentional change was introdu...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / minigames / minigame / ttt.qc
1 #include "ttt.qh"
2 REGISTER_MINIGAME(ttt, _("Tic Tac Toe"));
3
4 const int TTT_TURN_PLACE = 0x0100; // player has to place a piece on the board
5 const int TTT_TURN_WIN   = 0x0200; // player has won
6 const int TTT_TURN_DRAW  = 0x0400; // no moves are possible
7 const int TTT_TURN_NEXT  = 0x0800; // a player wants to start a new match
8 const int TTT_TURN_TYPE  = 0x0f00; // turn type mask
9
10 const int TTT_TURN_TEAM1 = 0x0001;
11 const int TTT_TURN_TEAM2 = 0x0002;
12 const int TTT_TURN_TEAM  = 0x000f; // turn team mask
13
14 // send flags
15 const int TTT_SF_PLAYERSCORE  = MINIG_SF_CUSTOM;   // send minigame_player scores (won matches)
16 const int TTT_SF_SINGLEPLAYER = MINIG_SF_CUSTOM<<1;// send minigame.ttt_ai
17
18 const int TTT_SPECTATOR_TEAM = 255; // must be above max teams and equal to or below 255
19
20 const int TTT_LET_CNT = 3;
21 const int TTT_NUM_CNT = 3;
22 const int TTT_TILE_SIZE = 3;
23
24 .int ttt_npieces; // (minigame) number of pieces on the board (simplifies checking a draw)
25 .int ttt_nexteam; // (minigame) next team (used to change the starting team on following matches)
26 .int ttt_ai;      // (minigame) when non-zero, singleplayer vs AI
27
28 // find tic tac toe piece given its tile name
29 entity ttt_find_piece(entity minig, string tile)
30 {
31         entity e = NULL;
32         while ( ( e = findentity(e,owner,minig) ) )
33                 if ( e.classname == "minigame_board_piece" && e.netname == tile )
34                         return e;
35         return NULL;
36 }
37
38 // Checks if the given piece completes a row
39 bool ttt_winning_piece(entity piece)
40 {
41         int number = minigame_tile_number(piece.netname);
42         int letter = minigame_tile_letter(piece.netname);
43
44         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(0,number)).team == piece.team )
45         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(1,number)).team == piece.team )
46         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(2,number)).team == piece.team )
47                 return true;
48
49         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(letter,0)).team == piece.team )
50         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(letter,1)).team == piece.team )
51         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(letter,2)).team == piece.team )
52                 return true;
53
54         if ( number == letter )
55         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(0,0)).team == piece.team )
56         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(1,1)).team == piece.team )
57         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(2,2)).team == piece.team )
58                 return true;
59
60         if ( number == 2-letter )
61         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(0,2)).team == piece.team )
62         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(1,1)).team == piece.team )
63         if ( ttt_find_piece(piece.owner,minigame_tile_buildname(2,0)).team == piece.team )
64                 return true;
65
66         return false;
67 }
68
69 // check if the tile name is valid (3x3 grid)
70 bool ttt_valid_tile(string tile)
71 {
72         if ( !tile )
73                 return 0;
74         int number = minigame_tile_number(tile);
75         int letter = minigame_tile_letter(tile);
76         return 0 <= number && number < TTT_NUM_CNT && 0 <= letter && letter < TTT_LET_CNT;
77 }
78
79 // make a move
80 void ttt_move(entity minigame, entity player, string pos )
81 {
82         if ( minigame.minigame_flags & TTT_TURN_PLACE )
83         if ( pos && player.team == (minigame.minigame_flags & TTT_TURN_TEAM) )
84         {
85                 if ( ttt_valid_tile(pos) )
86                 if ( !ttt_find_piece(minigame,pos) )
87                 {
88                         entity piece = msle_spawn(minigame,new(minigame_board_piece));
89                         piece.team = player.team;
90                         piece.netname = strzone(pos);
91                         minigame_server_sendflags(piece,MINIG_SF_ALL);
92                         minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
93                         minigame.ttt_npieces++;
94                         minigame.ttt_nexteam = minigame_next_team(player.team,2);
95                         if ( ttt_winning_piece(piece) )
96                         {
97                                 player.minigame_flags++;
98                                 minigame_server_sendflags(player, TTT_SF_PLAYERSCORE);
99                                 minigame.minigame_flags = TTT_TURN_WIN | player.team;
100                         }
101                         else if ( minigame.ttt_npieces >= (TTT_LET_CNT * TTT_NUM_CNT) )
102                                 minigame.minigame_flags = TTT_TURN_DRAW;
103                         else
104                                 minigame.minigame_flags = TTT_TURN_PLACE | minigame.ttt_nexteam;
105                 }
106         }
107 }
108
109 // request a new match
110 void ttt_next_match(entity minigame, entity player)
111 {
112 #ifdef SVQC
113         // on multiplayer matches, wait for both players to agree
114         if ( minigame.minigame_flags & (TTT_TURN_WIN|TTT_TURN_DRAW) )
115         {
116                 minigame.minigame_flags = TTT_TURN_NEXT | player.team;
117                 minigame.SendFlags |= MINIG_SF_UPDATE;
118         }
119         else if ( (minigame.minigame_flags & TTT_TURN_NEXT) &&
120                         !( minigame.minigame_flags & player.team ) )
121 #endif
122         {
123                 minigame.minigame_flags = TTT_TURN_PLACE | minigame.ttt_nexteam;
124                 minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
125                 minigame.ttt_npieces = 0;
126                 entity e = NULL;
127                 while ( ( e = findentity(e,owner,minigame) ) )
128                         if ( e.classname == "minigame_board_piece" )
129                                 delete(e);
130         }
131 }
132
133 #ifdef SVQC
134
135
136 // required function, handle server side events
137 int ttt_server_event(entity minigame, string event, ...)
138 {
139         switch(event)
140         {
141                 case "start":
142                 {
143                         minigame.minigame_flags = (TTT_TURN_PLACE | TTT_TURN_TEAM1);
144                         return true;
145                 }
146                 case "end":
147                 {
148                         entity e = NULL;
149                         while( (e = findentity(e, owner, minigame)) )
150                         if(e.classname == "minigame_board_piece")
151                         {
152                                 strfree(e.netname);
153                                 delete(e);
154                         }
155                         return false;
156                 }
157                 case "join":
158                 {
159                         int pl_num = minigame_count_players(minigame);
160
161                         // Don't allow joining a single player match
162                         if ( (minigame.ttt_ai) && pl_num > 0 )
163                                 return false;
164
165                         // Don't allow more than 2 players
166                         if(pl_num >= 2) { return TTT_SPECTATOR_TEAM; }
167
168                         // Get the right team
169                         if(minigame.minigame_players)
170                                 return minigame_next_team(minigame.minigame_players.team, 2);
171
172                         // Team 1 by default
173                         return 1;
174                 }
175                 case "cmd":
176                 {
177                         entity player = ...(0,entity);
178                         bool event_blocked = (player.team == TTT_SPECTATOR_TEAM);
179                         switch(argv(0))
180                         {
181                                 case "move":
182                                         if(event_blocked)
183                                                 return true;
184                                         ttt_move(minigame, ...(0,entity), ...(1,int) == 2 ? argv(1) : string_null );
185                                         return true;
186                                 case "next":
187                                         if(event_blocked)
188                                                 return true;
189                                         ttt_next_match(minigame,...(0,entity));
190                                         return true;
191                                 case "singleplayer":
192                                         if(event_blocked)
193                                                 return true;
194                                         if ( minigame_count_players(minigame) == 1 )
195                                         {
196                                                 minigame.ttt_ai = minigame_next_team(minigame.minigame_players.team, 2);
197                                                 minigame.SendFlags = TTT_SF_SINGLEPLAYER;
198                                         }
199                                         return true;
200                         }
201
202                         return false;
203                 }
204                 case "network_send":
205                 {
206                         entity sent = ...(0,entity);
207                         int sf = ...(1,int);
208                         if ( sent.classname == "minigame_player" && (sf & TTT_SF_PLAYERSCORE ) )
209                         {
210                                 WriteByte(MSG_ENTITY,sent.minigame_flags);
211                         }
212                         else if ( sent.classname == "minigame" && (sf & TTT_SF_SINGLEPLAYER) )
213                         {
214                                 WriteByte(MSG_ENTITY,sent.ttt_ai);
215                         }
216                         return false;
217                 }
218         }
219
220         return false;
221 }
222
223
224 #elif defined(CSQC)
225
226 string ttt_curr_pos; // identifier of the tile under the mouse
227 vector ttt_boardpos; // HUD board position
228 vector ttt_boardsize;// HUD board size
229 .int ttt_checkwin; // Used to optimize checks to display a win
230
231 // Required function, draw the game board
232 void ttt_hud_board(vector pos, vector mySize)
233 {
234         minigame_hud_fitsqare(pos, mySize);
235         ttt_boardpos = pos;
236         ttt_boardsize = mySize;
237
238         minigame_hud_simpleboard(pos,mySize,minigame_texture("ttt/board"));
239
240         vector tile_size = minigame_hud_denormalize_size('1 1 0'/TTT_TILE_SIZE,pos,mySize);
241         vector tile_pos;
242
243         if ( (active_minigame.minigame_flags & TTT_TURN_TEAM) == minigame_self.team )
244         if ( ttt_valid_tile(ttt_curr_pos) )
245         {
246                 tile_pos = minigame_tile_pos(ttt_curr_pos,TTT_LET_CNT,TTT_NUM_CNT);
247                 tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
248                 minigame_drawpic_centered( tile_pos,
249                                 minigame_texture(strcat("ttt/piece",ftos(minigame_self.team))),
250                                 tile_size, '1 1 1', panel_fg_alpha/2, DRAWFLAG_NORMAL );
251         }
252
253         entity e;
254         FOREACH_MINIGAME_ENTITY(e)
255         {
256                 if ( e.classname == "minigame_board_piece" )
257                 {
258                         tile_pos = minigame_tile_pos(e.netname,TTT_LET_CNT,TTT_NUM_CNT);
259                         tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
260
261                         if ( active_minigame.minigame_flags & TTT_TURN_WIN )
262                         if ( !e.ttt_checkwin )
263                                 e.ttt_checkwin = ttt_winning_piece(e) ? 1 : -1;
264
265                         float icon_color = 1;
266                         if ( e.ttt_checkwin == -1 )
267                                 icon_color = 0.4;
268                         else if ( e.ttt_checkwin == 1 )
269                         {
270                                 icon_color = 2;
271                                 minigame_drawpic_centered( tile_pos, minigame_texture("ttt/winglow"),
272                                                 tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_ADDITIVE );
273                         }
274
275                         minigame_drawpic_centered( tile_pos,
276                                         minigame_texture(strcat("ttt/piece",ftos(e.team))),
277                                         tile_size, '1 1 1'*icon_color, panel_fg_alpha, DRAWFLAG_NORMAL );
278                 }
279         }
280 }
281
282
283 // Required function, draw the game status panel
284 void ttt_hud_status(vector pos, vector mySize)
285 {
286         HUD_Panel_DrawBg();
287         vector ts;
288         ts = minigame_drawstring_wrapped(mySize_x,pos,active_minigame.descriptor.message,
289                 hud_fontsize * 2, '0.25 0.47 0.72', panel_fg_alpha, DRAWFLAG_NORMAL,0.5);
290
291         pos_y += ts_y;
292         mySize_y -= ts_y;
293
294         vector player_fontsize = hud_fontsize * 1.75;
295         ts_y = ( mySize_y - 2*player_fontsize_y ) / 2;
296         ts_x = mySize_x;
297         vector mypos;
298         vector tile_size = '48 48 0';
299
300         entity e;
301         FOREACH_MINIGAME_ENTITY(e)
302         {
303                 if ( e.classname == "minigame_player" && e.team != TTT_SPECTATOR_TEAM )
304                 {
305                         mypos = pos;
306                         if ( e.team == 2 )
307                                 mypos_y  += player_fontsize_y + ts_y;
308                         minigame_drawcolorcodedstring_trunc(mySize_x,mypos,
309                                 (e.minigame_playerslot ? entcs_GetName(e.minigame_playerslot-1) : _("AI")),
310                                 player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
311
312                         mypos_y += player_fontsize_y;
313                         drawpic( mypos,
314                                         minigame_texture(strcat("ttt/piece",ftos(e.team))),
315                                         tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
316
317                         mypos_x += tile_size_x;
318
319                         drawstring(mypos,ftos(e.minigame_flags),tile_size,
320                                            '0.7 0.84 1', panel_fg_alpha, DRAWFLAG_NORMAL);
321                 }
322         }
323 }
324
325 // Turn a set of flags into a help message
326 string ttt_turn_to_string(int turnflags)
327 {
328         if(minigame_self.team == TTT_SPECTATOR_TEAM)
329                 return _("You are spectating");
330
331         if ( turnflags & TTT_TURN_DRAW )
332                 return _("Draw");
333
334         if ( turnflags & TTT_TURN_WIN )
335         {
336                 // translator-friendly messages composed of 2 existing messages
337                 // TODO: proper "you win" banner instead of hijacking the help message
338                 if ( (turnflags & TTT_TURN_TEAM) != minigame_self.team )
339                         return strcat(_("You lost the game!"), "\n", _("Select \"^1Next Match^7\" on the menu for a rematch!"));
340                 return strcat(_("You win!"), "\n", _("Select \"^1Next Match^7\" on the menu to start a new match!"));
341         }
342
343         if ( turnflags & TTT_TURN_NEXT )
344         {
345                 if ( (turnflags & TTT_TURN_TEAM) != minigame_self.team )
346                         return _("Select \"^1Next Match^7\" on the menu to start a new match!");
347                 return _("Wait for your opponent to confirm the rematch");
348         }
349
350         if ( (turnflags & TTT_TURN_TEAM) != minigame_self.team )
351                 return _("Wait for your opponent to make their move");
352
353         if ( turnflags & TTT_TURN_PLACE )
354                 return _("Click on the game board to place your piece");
355
356         return "";
357 }
358
359 const int TTT_AI_POSFLAG_A1 = 0x0001;
360 const int TTT_AI_POSFLAG_A2 = 0x0002;
361 const int TTT_AI_POSFLAG_A3 = 0x0004;
362 const int TTT_AI_POSFLAG_B1 = 0x0008;
363 const int TTT_AI_POSFLAG_B2 = 0x0010;
364 const int TTT_AI_POSFLAG_B3 = 0x0020;
365 const int TTT_AI_POSFLAG_C1 = 0x0040;
366 const int TTT_AI_POSFLAG_C2 = 0x0080;
367 const int TTT_AI_POSFLAG_C3 = 0x0100;
368
369 // convert a flag to a position
370 string ttt_ai_piece_flag2pos(int pieceflag)
371 {
372         switch(pieceflag)
373         {
374                 case TTT_AI_POSFLAG_A1:
375                         return "a1";
376                 case TTT_AI_POSFLAG_A2:
377                         return "a2";
378                 case TTT_AI_POSFLAG_A3:
379                         return "a3";
380
381                 case TTT_AI_POSFLAG_B1:
382                         return "b1";
383                 case TTT_AI_POSFLAG_B2:
384                         return "b2";
385                 case TTT_AI_POSFLAG_B3:
386                         return "b3";
387
388                 case TTT_AI_POSFLAG_C1:
389                         return "c1";
390                 case TTT_AI_POSFLAG_C2:
391                         return "c2";
392                 case TTT_AI_POSFLAG_C3:
393                         return "c3";
394
395                 default:
396                         return string_null;
397         }
398 }
399
400 bool ttt_ai_checkmask(int piecemask, int checkflags)
401 {
402         return checkflags && (piecemask & checkflags) == checkflags;
403 }
404
405 // get the third flag if the mask matches two of them
406 int ttt_ai_1of3(int piecemask, int flag1, int flag2, int flag3)
407 {
408         if ( ttt_ai_checkmask(piecemask,flag1|flag2|flag3) )
409                 return 0;
410
411         if ( ttt_ai_checkmask(piecemask,flag1|flag2) )
412                 return flag3;
413
414         if ( ttt_ai_checkmask(piecemask,flag3|flag2) )
415                 return flag1;
416
417         if ( ttt_ai_checkmask(piecemask,flag3|flag1) )
418                 return flag2;
419
420         return 0;
421 }
422
423 // Select a random flag in the mask
424 int ttt_ai_random(int piecemask)
425 {
426         if ( !piecemask )
427                 return 0;
428
429         int f = 1;
430
431         RandomSelection_Init();
432
433         for ( int i = 0; i < 9; i++ )
434         {
435                 if ( piecemask & f )
436                         RandomSelection_AddFloat(f, 1, 1);
437                 f <<= 1;
438         }
439
440         LOG_TRACE(sprintf("TTT AI: selected %x from %x",
441                         RandomSelection_chosen_float, piecemask) );
442         return RandomSelection_chosen_float;
443 }
444
445 // Block/complete a 3 i na row
446 int ttt_ai_block3 ( int piecemask, int piecemask_free )
447 {
448         int r = 0;
449
450         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_A2,TTT_AI_POSFLAG_A3);
451         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_B1,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_B3);
452         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_C1,TTT_AI_POSFLAG_C2,TTT_AI_POSFLAG_C3);
453         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_B1,TTT_AI_POSFLAG_C1);
454         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A2,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C2);
455         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A3,TTT_AI_POSFLAG_B3,TTT_AI_POSFLAG_C3);
456         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C3);
457         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A3,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C1);
458         LOG_TRACE(sprintf("TTT AI: possible 3 in a rows in %x: %x (%x)",piecemask,r, r&piecemask_free));
459         r &= piecemask_free;
460         return ttt_ai_random(r);
461 }
462
463 // Simple AI
464 // 1) tries to win the game if possible
465 // 2) tries to block the opponent if they have 2 in a row
466 // 3) places a piece randomly
467 string ttt_ai_choose_simple(int piecemask_self, int piecemask_opponent, int piecemask_free )
468 {
469         int move = 0;
470
471         LOG_TRACE("TTT AI: checking winning move");
472         if (( move = ttt_ai_block3(piecemask_self,piecemask_free) ))
473                 return ttt_ai_piece_flag2pos(move); // place winning move
474
475         LOG_TRACE("TTT AI: checking opponent's winning move");
476         if (( move = ttt_ai_block3(piecemask_opponent,piecemask_free) ))
477                 return ttt_ai_piece_flag2pos(move); // block opponent
478
479         LOG_TRACE("TTT AI: random move");
480         return ttt_ai_piece_flag2pos(ttt_ai_random(piecemask_free));
481 }
482
483 // AI move (if it's AI's turn)
484 void ttt_aimove(entity minigame)
485 {
486         if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame.ttt_ai) )
487         {
488                 entity aiplayer = NULL;
489                 while ( ( aiplayer = findentity(aiplayer,owner,minigame) ) )
490                         if ( aiplayer.classname == "minigame_player" && !aiplayer.minigame_playerslot )
491                                 break;
492
493                 /*
494                  * Build bit masks for the board pieces
495                  * .---.---.---.
496                  * | 4 | 32|256| 3
497                  * |---+---+---|
498                  * | 2 | 16|128| 2
499                  * |---+---+---|
500                  * | 1 | 8 | 64| 1
501                  * '---'---'---'
502                  *   A   B   C
503                  */
504                 int piecemask_self = 0;
505                 int piecemask_opponent = 0;
506                 int piecemask_free = 0;
507                 int pieceflag = 1;
508                 string pos;
509                 for ( int i = 0; i < 3; i++ )
510                 {
511                         for ( int j = 0; j < 3; j++ )
512                         {
513                                 pos = minigame_tile_buildname(i,j);
514                                 entity piece = ttt_find_piece(minigame,pos);
515                                 if ( piece )
516                                 {
517                                         if ( piece.team == aiplayer.team )
518                                                 piecemask_self |= pieceflag;
519                                         else
520                                                 piecemask_opponent |= pieceflag;
521                                 }
522                                 else
523                                         piecemask_free |= pieceflag;
524                                 pieceflag <<= 1;
525                         }
526                 }
527
528                 // TODO multiple AI difficulties
529                 LOG_TRACE(sprintf("TTT AI: self: %x opponent: %x free: %x",
530                                 piecemask_self, piecemask_opponent, piecemask_free));
531                 pos = ttt_ai_choose_simple(piecemask_self, piecemask_opponent, piecemask_free);
532                 LOG_TRACE("TTT AI: chosen move: ", pos);
533                 if ( !pos )
534                         LOG_TRACE("Tic Tac Toe AI has derped!");
535                 else
536                         ttt_move(minigame,aiplayer,pos);
537         }
538         strcpy(minigame.message, ttt_turn_to_string(minigame.minigame_flags));
539 }
540
541 // Make the correct move
542 void ttt_make_move(entity minigame)
543 {
544         if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame_self.team) )
545         {
546                 if ( minigame.ttt_ai  )
547                 {
548                         ttt_move(minigame, minigame_self, ttt_curr_pos );
549                         ttt_aimove(minigame);
550                 }
551                 else
552                         minigame_cmd("move ",ttt_curr_pos);
553         }
554 }
555
556 void ttt_set_curr_pos(string s)
557 {
558         strfree(ttt_curr_pos);
559         if ( s )
560                 s = strzone(s);
561         ttt_curr_pos = s;
562 }
563
564 // Required function, handle client events
565 int ttt_client_event(entity minigame, string event, ...)
566 {
567         switch(event)
568         {
569                 case "activate":
570                 {
571                         ttt_set_curr_pos("");
572                         strcpy(minigame.message, ttt_turn_to_string(minigame.minigame_flags));
573                         return false;
574                 }
575                 case "deactivate":
576                 {
577                         strfree(minigame.message);
578                         return false;
579                 }
580                 case "key_pressed":
581                 case "key_released":
582                 {
583                         bool event_blocked = ((event == "key_released")
584                                 || ((minigame.minigame_flags & TTT_TURN_TEAM) != minigame_self.team));
585                         if (!(minigame.minigame_flags & TTT_TURN_WIN) && !(minigame.minigame_flags & TTT_TURN_DRAW))
586                         {
587                                 switch ( ...(0,int) )
588                                 {
589                                         case K_RIGHTARROW:
590                                         case K_KP_RIGHTARROW:
591                                                 if (event_blocked)
592                                                         return true;
593                                                 if ( ! ttt_curr_pos )
594                                                         ttt_set_curr_pos("a3");
595                                                 else
596                                                         ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,1,0,TTT_LET_CNT,TTT_NUM_CNT));
597                                                 return true;
598                                         case K_LEFTARROW:
599                                         case K_KP_LEFTARROW:
600                                                 if (event_blocked)
601                                                         return true;
602                                                 if ( ! ttt_curr_pos )
603                                                         ttt_set_curr_pos("c3");
604                                                 else
605                                                         ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,-1,0,TTT_LET_CNT,TTT_NUM_CNT));
606                                                 return true;
607                                         case K_UPARROW:
608                                         case K_KP_UPARROW:
609                                                 if (event_blocked)
610                                                         return true;
611                                                 if ( ! ttt_curr_pos )
612                                                         ttt_set_curr_pos("a1");
613                                                 else
614                                                         ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,0,1,TTT_LET_CNT,TTT_NUM_CNT));
615                                                 return true;
616                                         case K_DOWNARROW:
617                                         case K_KP_DOWNARROW:
618                                                 if (event_blocked)
619                                                         return true;
620                                                 if ( ! ttt_curr_pos )
621                                                         ttt_set_curr_pos("a3");
622                                                 else
623                                                         ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,0,-1,TTT_LET_CNT,TTT_NUM_CNT));
624                                                 return true;
625                                         case K_ENTER:
626                                         case K_KP_ENTER:
627                                         case K_SPACE:
628                                                 if (event_blocked)
629                                                         return true;
630                                                 ttt_make_move(minigame);
631                                                 return true;
632                                 }
633                         }
634
635                         return false;
636                 }
637                 case "mouse_pressed":
638                 {
639                         if(...(0,int) == K_MOUSE1)
640                         {
641                                 ttt_client_event(minigame, "mouse_moved");
642                                 ttt_make_move(minigame);
643                                 return true;
644                         }
645
646                         return false;
647                 }
648                 case "mouse_moved":
649                 {
650                         vector mouse_pos = minigame_hud_normalize(mousepos,ttt_boardpos,ttt_boardsize);
651                         if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame_self.team) )
652                                 ttt_set_curr_pos(minigame_tile_name(mouse_pos,TTT_LET_CNT,TTT_NUM_CNT));
653                         if ( ! ttt_valid_tile(ttt_curr_pos) )
654                                 ttt_set_curr_pos("");
655
656                         return true;
657                 }
658                 case "network_receive":
659                 {
660                         entity sent = ...(0,entity);
661                         int sf = ...(1,int);
662                         if ( sent.classname == "minigame" )
663                         {
664                                 if ( sf & MINIG_SF_UPDATE )
665                                 {
666                                         strcpy(sent.message, ttt_turn_to_string(sent.minigame_flags));
667                                         if ( sent.minigame_flags & minigame_self.team )
668                                                 minigame_prompt();
669                                 }
670
671                                 if ( (sf & TTT_SF_SINGLEPLAYER) )
672                                 {
673                                         int ai = ReadByte();
674                                         bool spawnai = ai && !sent.ttt_ai;
675                                         sent.ttt_ai = ai;
676
677                                         if ( spawnai )
678                                         {
679                                                 entity aiplayer = new(minigame_player);
680                                                 aiplayer.owner = minigame;
681                                                 aiplayer.team = ai;
682                                                 aiplayer.minigame_playerslot = 0;
683                                                 aiplayer.minigame_autoclean = 1;
684                                                 ttt_aimove(minigame);
685                                         }
686
687                                 }
688                         }
689                         else if ( sent.classname == "minigame_player" && (sf & TTT_SF_PLAYERSCORE ) )
690                         {
691                                 sent.minigame_flags = ReadByte();
692                         }
693
694                         return false;
695                 }
696                 case "menu_show":
697                 {
698                         HUD_MinigameMenu_CustomEntry(...(0,entity),_("Next Match"),"next");
699                         HUD_MinigameMenu_CustomEntry(...(0,entity),_("Single Player"),"singleplayer");
700                         return false;
701                 }
702                 case "menu_click":
703                 {
704                         if(...(0,string) == "next")
705                         {
706                                 if ( minigame.ttt_ai )
707                                 {
708                                         ttt_next_match(minigame,minigame_self);
709                                         ttt_aimove(minigame);
710                                 }
711                                 else
712                                         minigame_cmd("next");
713                         }
714                         else if ( ...(0,string) == "singleplayer" && !minigame.ttt_ai )
715                         {
716                                 if ( minigame_count_players(minigame) == 1 )
717                                         minigame_cmd("singleplayer");
718                         }
719                         return false;
720                 }
721         }
722
723         return false;
724 }
725
726 #endif