]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/minigames/minigame/ttt.qc
Show spectator list on all minigames
[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         minigame_show_allspecs(ttt_boardpos, ttt_boardsize);
282 }
283
284
285 // Required function, draw the game status panel
286 void ttt_hud_status(vector pos, vector mySize)
287 {
288         HUD_Panel_DrawBg();
289         vector ts;
290         ts = minigame_drawstring_wrapped(mySize_x,pos,active_minigame.descriptor.message,
291                 hud_fontsize * 2, '0.25 0.47 0.72', panel_fg_alpha, DRAWFLAG_NORMAL,0.5);
292
293         pos_y += ts_y;
294         mySize_y -= ts_y;
295
296         vector player_fontsize = hud_fontsize * 1.75;
297         ts_y = ( mySize_y - 2*player_fontsize_y ) / 2;
298         ts_x = mySize_x;
299         vector mypos;
300         vector tile_size = '48 48 0';
301
302         entity e;
303         FOREACH_MINIGAME_ENTITY(e)
304         {
305                 if ( e.classname == "minigame_player" && e.team != TTT_SPECTATOR_TEAM )
306                 {
307                         mypos = pos;
308                         if ( e.team == 2 )
309                                 mypos_y  += player_fontsize_y + ts_y;
310                         minigame_drawcolorcodedstring_trunc(mySize_x,mypos,
311                                 (e.minigame_playerslot ? entcs_GetName(e.minigame_playerslot-1) : _("AI")),
312                                 player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
313
314                         mypos_y += player_fontsize_y;
315                         drawpic( mypos,
316                                         minigame_texture(strcat("ttt/piece",ftos(e.team))),
317                                         tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
318
319                         mypos_x += tile_size_x;
320
321                         drawstring(mypos,ftos(e.minigame_flags),tile_size,
322                                         '0.7 0.84 1', panel_fg_alpha, DRAWFLAG_NORMAL);
323                 }
324         }
325 }
326
327 // Turn a set of flags into a help message
328 string ttt_turn_to_string(int turnflags)
329 {
330         if(minigame_self.team == TTT_SPECTATOR_TEAM)
331                 return _("You are spectating");
332
333         if ( turnflags & TTT_TURN_DRAW )
334                 return _("Draw");
335
336         if ( turnflags & TTT_TURN_WIN )
337         {
338                 // translator-friendly messages composed of 2 existing messages
339                 // TODO: proper "you win" banner instead of hijacking the help message
340                 if ( (turnflags & TTT_TURN_TEAM) != minigame_self.team )
341                         return strcat(_("You lost the game!"), "\n", _("Select \"^1Next Match^7\" on the menu for a rematch!"));
342                 return strcat(_("You win!"), "\n", _("Select \"^1Next Match^7\" on the menu to start a new match!"));
343         }
344
345         if ( turnflags & TTT_TURN_NEXT )
346         {
347                 if ( (turnflags & TTT_TURN_TEAM) != minigame_self.team )
348                         return _("Select \"^1Next Match^7\" on the menu to start a new match!");
349                 return _("Wait for your opponent to confirm the rematch");
350         }
351
352         if ( (turnflags & TTT_TURN_TEAM) != minigame_self.team )
353                 return _("Wait for your opponent to make their move");
354
355         if ( turnflags & TTT_TURN_PLACE )
356                 return _("Click on the game board to place your piece");
357
358         return "";
359 }
360
361 const int TTT_AI_POSFLAG_A1 = 0x0001;
362 const int TTT_AI_POSFLAG_A2 = 0x0002;
363 const int TTT_AI_POSFLAG_A3 = 0x0004;
364 const int TTT_AI_POSFLAG_B1 = 0x0008;
365 const int TTT_AI_POSFLAG_B2 = 0x0010;
366 const int TTT_AI_POSFLAG_B3 = 0x0020;
367 const int TTT_AI_POSFLAG_C1 = 0x0040;
368 const int TTT_AI_POSFLAG_C2 = 0x0080;
369 const int TTT_AI_POSFLAG_C3 = 0x0100;
370
371 // convert a flag to a position
372 string ttt_ai_piece_flag2pos(int pieceflag)
373 {
374         switch(pieceflag)
375         {
376                 case TTT_AI_POSFLAG_A1:
377                         return "a1";
378                 case TTT_AI_POSFLAG_A2:
379                         return "a2";
380                 case TTT_AI_POSFLAG_A3:
381                         return "a3";
382
383                 case TTT_AI_POSFLAG_B1:
384                         return "b1";
385                 case TTT_AI_POSFLAG_B2:
386                         return "b2";
387                 case TTT_AI_POSFLAG_B3:
388                         return "b3";
389
390                 case TTT_AI_POSFLAG_C1:
391                         return "c1";
392                 case TTT_AI_POSFLAG_C2:
393                         return "c2";
394                 case TTT_AI_POSFLAG_C3:
395                         return "c3";
396
397                 default:
398                         return string_null;
399         }
400 }
401
402 bool ttt_ai_checkmask(int piecemask, int checkflags)
403 {
404         return checkflags && (piecemask & checkflags) == checkflags;
405 }
406
407 // get the third flag if the mask matches two of them
408 int ttt_ai_1of3(int piecemask, int flag1, int flag2, int flag3)
409 {
410         if ( ttt_ai_checkmask(piecemask,flag1|flag2|flag3) )
411                 return 0;
412
413         if ( ttt_ai_checkmask(piecemask,flag1|flag2) )
414                 return flag3;
415
416         if ( ttt_ai_checkmask(piecemask,flag3|flag2) )
417                 return flag1;
418
419         if ( ttt_ai_checkmask(piecemask,flag3|flag1) )
420                 return flag2;
421
422         return 0;
423 }
424
425 // Select a random flag in the mask
426 int ttt_ai_random(int piecemask)
427 {
428         if ( !piecemask )
429                 return 0;
430
431         int f = 1;
432
433         RandomSelection_Init();
434
435         for ( int i = 0; i < 9; ++i )
436         {
437                 if ( piecemask & f )
438                         RandomSelection_AddFloat(f, 1, 1);
439                 f <<= 1;
440         }
441
442         LOG_TRACE(sprintf("TTT AI: selected %x from %x",
443                         RandomSelection_chosen_float, piecemask) );
444         return RandomSelection_chosen_float;
445 }
446
447 // Block/complete a 3 i na row
448 int ttt_ai_block3 ( int piecemask, int piecemask_free )
449 {
450         int r = 0;
451
452         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_A2,TTT_AI_POSFLAG_A3);
453         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_B1,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_B3);
454         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_C1,TTT_AI_POSFLAG_C2,TTT_AI_POSFLAG_C3);
455         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_B1,TTT_AI_POSFLAG_C1);
456         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A2,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C2);
457         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A3,TTT_AI_POSFLAG_B3,TTT_AI_POSFLAG_C3);
458         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C3);
459         r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A3,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C1);
460         LOG_TRACE(sprintf("TTT AI: possible 3 in a rows in %x: %x (%x)",piecemask,r, r&piecemask_free));
461         r &= piecemask_free;
462         return ttt_ai_random(r);
463 }
464
465 // Simple AI
466 // 1) tries to win the game if possible
467 // 2) tries to block the opponent if they have 2 in a row
468 // 3) places a piece randomly
469 string ttt_ai_choose_simple(int piecemask_self, int piecemask_opponent, int piecemask_free )
470 {
471         int move = 0;
472
473         LOG_TRACE("TTT AI: checking winning move");
474         if (( move = ttt_ai_block3(piecemask_self,piecemask_free) ))
475                 return ttt_ai_piece_flag2pos(move); // place winning move
476
477         LOG_TRACE("TTT AI: checking opponent's winning move");
478         if (( move = ttt_ai_block3(piecemask_opponent,piecemask_free) ))
479                 return ttt_ai_piece_flag2pos(move); // block opponent
480
481         LOG_TRACE("TTT AI: random move");
482         return ttt_ai_piece_flag2pos(ttt_ai_random(piecemask_free));
483 }
484
485 // AI move (if it's AI's turn)
486 void ttt_aimove(entity minigame)
487 {
488         if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame.ttt_ai) )
489         {
490                 entity aiplayer = NULL;
491                 while ( ( aiplayer = findentity(aiplayer,owner,minigame) ) )
492                         if ( aiplayer.classname == "minigame_player" && !aiplayer.minigame_playerslot )
493                                 break;
494
495                 /*
496                  * Build bit masks for the board pieces
497                  * .---.---.---.
498                  * | 4 | 32|256| 3
499                  * |---+---+---|
500                  * | 2 | 16|128| 2
501                  * |---+---+---|
502                  * | 1 | 8 | 64| 1
503                  * '---'---'---'
504                  *   A   B   C
505                  */
506                 int piecemask_self = 0;
507                 int piecemask_opponent = 0;
508                 int piecemask_free = 0;
509                 int pieceflag = 1;
510                 string pos;
511                 for ( int i = 0; i < 3; ++i )
512                 {
513                         for ( int j = 0; j < 3; ++j )
514                         {
515                                 pos = minigame_tile_buildname(i,j);
516                                 entity piece = ttt_find_piece(minigame,pos);
517                                 if ( piece )
518                                 {
519                                         if ( piece.team == aiplayer.team )
520                                                 piecemask_self |= pieceflag;
521                                         else
522                                                 piecemask_opponent |= pieceflag;
523                                 }
524                                 else
525                                         piecemask_free |= pieceflag;
526                                 pieceflag <<= 1;
527                         }
528                 }
529
530                 // TODO multiple AI difficulties
531                 LOG_TRACE(sprintf("TTT AI: self: %x opponent: %x free: %x",
532                                 piecemask_self, piecemask_opponent, piecemask_free));
533                 pos = ttt_ai_choose_simple(piecemask_self, piecemask_opponent, piecemask_free);
534                 LOG_TRACE("TTT AI: chosen move: ", pos);
535                 if ( !pos )
536                         LOG_TRACE("Tic Tac Toe AI has derped!");
537                 else
538                         ttt_move(minigame,aiplayer,pos);
539         }
540         strcpy(minigame.message, ttt_turn_to_string(minigame.minigame_flags));
541 }
542
543 // Make the correct move
544 void ttt_make_move(entity minigame)
545 {
546         if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame_self.team) )
547         {
548                 if ( minigame.ttt_ai  )
549                 {
550                         ttt_move(minigame, minigame_self, ttt_curr_pos );
551                         ttt_aimove(minigame);
552                 }
553                 else
554                         minigame_cmd("move ",ttt_curr_pos);
555         }
556 }
557
558 void ttt_set_curr_pos(string s)
559 {
560         strfree(ttt_curr_pos);
561         if ( s )
562                 s = strzone(s);
563         ttt_curr_pos = s;
564 }
565
566 // Required function, handle client events
567 int ttt_client_event(entity minigame, string event, ...)
568 {
569         switch(event)
570         {
571                 case "activate":
572                 {
573                         ttt_set_curr_pos("");
574                         strcpy(minigame.message, ttt_turn_to_string(minigame.minigame_flags));
575                         return false;
576                 }
577                 case "deactivate":
578                 {
579                         strfree(minigame.message);
580                         return false;
581                 }
582                 case "key_pressed":
583                 case "key_released":
584                 {
585                         bool event_blocked = ((event == "key_released")
586                                 || ((minigame.minigame_flags & TTT_TURN_TEAM) != minigame_self.team));
587                         if (!(minigame.minigame_flags & TTT_TURN_WIN) && !(minigame.minigame_flags & TTT_TURN_DRAW))
588                         {
589                                 switch ( ...(0,int) )
590                                 {
591                                         case K_RIGHTARROW:
592                                         case K_KP_RIGHTARROW:
593                                                 if (event_blocked)
594                                                         return true;
595                                                 if ( ! ttt_curr_pos )
596                                                         ttt_set_curr_pos("a3");
597                                                 else
598                                                         ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,1,0,TTT_LET_CNT,TTT_NUM_CNT));
599                                                 return true;
600                                         case K_LEFTARROW:
601                                         case K_KP_LEFTARROW:
602                                                 if (event_blocked)
603                                                         return true;
604                                                 if ( ! ttt_curr_pos )
605                                                         ttt_set_curr_pos("c3");
606                                                 else
607                                                         ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,-1,0,TTT_LET_CNT,TTT_NUM_CNT));
608                                                 return true;
609                                         case K_UPARROW:
610                                         case K_KP_UPARROW:
611                                                 if (event_blocked)
612                                                         return true;
613                                                 if ( ! ttt_curr_pos )
614                                                         ttt_set_curr_pos("a1");
615                                                 else
616                                                         ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,0,1,TTT_LET_CNT,TTT_NUM_CNT));
617                                                 return true;
618                                         case K_DOWNARROW:
619                                         case K_KP_DOWNARROW:
620                                                 if (event_blocked)
621                                                         return true;
622                                                 if ( ! ttt_curr_pos )
623                                                         ttt_set_curr_pos("a3");
624                                                 else
625                                                         ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,0,-1,TTT_LET_CNT,TTT_NUM_CNT));
626                                                 return true;
627                                         case K_ENTER:
628                                         case K_KP_ENTER:
629                                         case K_SPACE:
630                                                 if (event_blocked)
631                                                         return true;
632                                                 ttt_make_move(minigame);
633                                                 return true;
634                                 }
635                         }
636
637                         return false;
638                 }
639                 case "mouse_pressed":
640                 {
641                         if(...(0,int) == K_MOUSE1)
642                         {
643                                 ttt_client_event(minigame, "mouse_moved");
644                                 ttt_make_move(minigame);
645                                 return true;
646                         }
647
648                         return false;
649                 }
650                 case "mouse_moved":
651                 {
652                         vector mouse_pos = minigame_hud_normalize(mousepos,ttt_boardpos,ttt_boardsize);
653                         if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame_self.team) )
654                                 ttt_set_curr_pos(minigame_tile_name(mouse_pos,TTT_LET_CNT,TTT_NUM_CNT));
655                         if ( ! ttt_valid_tile(ttt_curr_pos) )
656                                 ttt_set_curr_pos("");
657
658                         return true;
659                 }
660                 case "network_receive":
661                 {
662                         entity sent = ...(0,entity);
663                         int sf = ...(1,int);
664                         if ( sent.classname == "minigame" )
665                         {
666                                 if ( sf & MINIG_SF_UPDATE )
667                                 {
668                                         strcpy(sent.message, ttt_turn_to_string(sent.minigame_flags));
669                                         if ( sent.minigame_flags & minigame_self.team )
670                                                 minigame_prompt();
671                                 }
672
673                                 if ( (sf & TTT_SF_SINGLEPLAYER) )
674                                 {
675                                         int ai = ReadByte();
676                                         bool spawnai = ai && !sent.ttt_ai;
677                                         sent.ttt_ai = ai;
678
679                                         if ( spawnai )
680                                         {
681                                                 entity aiplayer = new(minigame_player);
682                                                 aiplayer.owner = minigame;
683                                                 aiplayer.team = ai;
684                                                 aiplayer.minigame_playerslot = 0;
685                                                 aiplayer.minigame_autoclean = 1;
686                                                 ttt_aimove(minigame);
687                                         }
688
689                                 }
690                         }
691                         else if ( sent.classname == "minigame_player" && (sf & TTT_SF_PLAYERSCORE ) )
692                         {
693                                 sent.minigame_flags = ReadByte();
694                         }
695
696                         return false;
697                 }
698                 case "menu_show":
699                 {
700                         HUD_MinigameMenu_CustomEntry(...(0,entity),_("Next Match"),"next");
701                         HUD_MinigameMenu_CustomEntry(...(0,entity),_("Single Player"),"singleplayer");
702                         return false;
703                 }
704                 case "menu_click":
705                 {
706                         if(...(0,string) == "next")
707                         {
708                                 if ( minigame.ttt_ai )
709                                 {
710                                         ttt_next_match(minigame,minigame_self);
711                                         ttt_aimove(minigame);
712                                 }
713                                 else
714                                         minigame_cmd("next");
715                         }
716                         else if ( ...(0,string) == "singleplayer" && !minigame.ttt_ai )
717                         {
718                                 if ( minigame_count_players(minigame) == 1 )
719                                         minigame_cmd("singleplayer");
720                         }
721                         return false;
722                 }
723         }
724
725         return false;
726 }
727
728 #endif