Merge branch 'master' into terencehill/hud_updates
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / minigames / minigame / c4.qc
1 #include "c4.qh"
2 REGISTER_MINIGAME(c4, "Connect Four");
3
4 const float C4_TURN_PLACE = 0x0100; // player has to place a piece on the board
5 const float C4_TURN_WIN   = 0x0200; // player has won
6 const float C4_TURN_DRAW  = 0x0400; // no moves are possible
7
8 const float C4_TURN_TEAM1 = 0x0001;
9 const float C4_TURN_TEAM2 = 0x0002;
10 const float C4_TURN_TEAM  = 0x000f; // turn team mask
11
12 const int C4_LET_CNT = 7;
13 const int C4_NUM_CNT = 6;
14 const int C4_WIN_CNT = 4;
15
16 const int C4_MAX_TILES = 42;
17
18 const int C4_TILE_SIZE = 8;
19
20 const int C4_TEAMS = 2;
21
22 .int c4_npieces; // (minigame) number of pieces on the board (simplifies checking a draw)
23 .int c4_nexteam; // (minigame) next team (used to change the starting team on following matches)
24
25 // find connect 4 piece given its tile name
26 entity c4_find_piece(entity minig, string tile)
27 {
28         entity e = NULL;
29         while ( ( e = findentity(e,owner,minig) ) )
30                 if ( e.classname == "minigame_board_piece" && e.netname == tile )
31                         return e;
32         return NULL;
33 }
34
35 // Checks if the given piece completes a row
36 bool c4_winning_piece(entity piece)
37 {
38         int number = minigame_tile_number(piece.netname);
39         int letter = minigame_tile_letter(piece.netname);
40
41         int i;
42         entity top = piece;
43         entity left = piece;
44         entity topleft = piece;
45         entity botleft = piece;
46         for(i = number; i < C4_NUM_CNT; ++i)
47         {
48                 entity p = c4_find_piece(piece.owner,minigame_tile_buildname(letter, i));
49                 if(p.team == piece.team)
50                         top = p;
51                 else break;
52         }
53
54         for(i = letter; i >= 0; --i)
55         {
56                 entity p = c4_find_piece(piece.owner,minigame_tile_buildname(i, number));
57                 if(p.team == piece.team)
58                         left = p;
59                 else break;
60         }
61
62         int j;
63         for(i = letter, j = number; i >= 0, j >= 0; --i, --j)
64         {
65                 entity p = c4_find_piece(piece.owner,minigame_tile_buildname(i, j));
66                 if(p.team == piece.team)
67                         botleft = p;
68                 else break;
69         }
70         for(i = letter, j = number; i >= 0, j < C4_NUM_CNT; --i, ++j)
71         {
72                 entity p = c4_find_piece(piece.owner,minigame_tile_buildname(i, j));
73                 if(p.team == piece.team)
74                         topleft = p;
75                 else break;
76         }
77
78         // down
79         int found = 0;
80         for(i = minigame_tile_number(top.netname); i >= 0; --i)
81         {
82                 if(c4_find_piece(piece.owner,minigame_tile_buildname(letter, i)).team == piece.team)
83                         ++found;
84                 else break;
85         }
86
87         if(found >= C4_WIN_CNT)
88                 return true;
89
90         // right
91         found = 0;
92         for(i = minigame_tile_letter(left.netname); i < C4_LET_CNT; ++i)
93         {
94                 if(c4_find_piece(piece.owner,minigame_tile_buildname(i, number)).team == piece.team)
95                         ++found;
96                 else break;
97         }
98
99         if(found >= C4_WIN_CNT)
100                 return true;
101
102         // diagright down
103         found = 0;
104         for(i = minigame_tile_letter(topleft.netname), j = minigame_tile_number(topleft.netname); i < C4_LET_CNT, j >= 0; ++i, --j)
105         {
106                 if(c4_find_piece(piece.owner,minigame_tile_buildname(i, j)).team == piece.team)
107                         ++found;
108                 else break;
109         }
110
111         if(found >= C4_WIN_CNT)
112                 return true;
113
114         // diagright up
115         found = 0;
116         for(i = minigame_tile_letter(botleft.netname), j = minigame_tile_number(botleft.netname); i < C4_LET_CNT, j < C4_NUM_CNT; ++i, ++j)
117         {
118                 if(c4_find_piece(piece.owner,minigame_tile_buildname(i, j)).team == piece.team)
119                         ++found;
120                 else break;
121         }
122
123         if(found >= C4_WIN_CNT)
124                 return true;
125
126         return false;
127 }
128
129 // check if the tile name is valid (6x7 grid)
130 bool c4_valid_tile(string tile)
131 {
132         if ( !tile )
133                 return false;
134         float number = minigame_tile_number(tile);
135         float letter = minigame_tile_letter(tile);
136         return 0 <= number && number < C4_NUM_CNT && 0 <= letter && letter < C4_LET_CNT;
137 }
138
139 string c4_get_lowest_tile(entity minigame, string s)
140 {
141         int i;
142         int end = 0;
143         for(i = C4_NUM_CNT; i >= 0; --i)
144         {
145                 if(!c4_find_piece(minigame,minigame_tile_buildname(minigame_tile_letter(s), i)))
146                 if(c4_find_piece(minigame,minigame_tile_buildname(minigame_tile_letter(s), i - 1)))
147                 {
148                         end = i;
149                         break;
150                 }
151         }
152         return minigame_tile_buildname(minigame_tile_letter(s), end);
153 }
154
155 // make a move
156 void c4_move(entity minigame, entity player, string pos )
157 {
158         pos = c4_get_lowest_tile(minigame, pos);
159
160         if ( minigame.minigame_flags & C4_TURN_PLACE )
161         if ( pos && player.team == (minigame.minigame_flags & C4_TURN_TEAM) )
162         {
163                 if ( c4_valid_tile(pos) )
164                 if ( !c4_find_piece(minigame,pos) )
165                 {
166                         entity piece = msle_spawn(minigame,"minigame_board_piece");
167                         piece.team = player.team;
168                         piece.netname = strzone(pos);
169                         minigame_server_sendflags(piece,MINIG_SF_ALL);
170                         minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
171                         minigame.c4_npieces++;
172                         minigame.c4_nexteam = minigame_next_team(player.team,C4_TEAMS);
173                         if ( c4_winning_piece(piece) )
174                         {
175                                 minigame.minigame_flags = C4_TURN_WIN | player.team;
176                         }
177                         else if ( minigame.c4_npieces >= C4_MAX_TILES )
178                                 minigame.minigame_flags = C4_TURN_DRAW;
179                         else
180                                 minigame.minigame_flags = C4_TURN_PLACE | minigame.c4_nexteam;
181                 }
182         }
183 }
184
185 #ifdef SVQC
186
187
188 // required function, handle server side events
189 int c4_server_event(entity minigame, string event, ...)
190 {
191         switch(event)
192         {
193                 case "start":
194                 {
195                         minigame.minigame_flags = (C4_TURN_PLACE | C4_TURN_TEAM1);
196                         return true;
197                 }
198                 case "end":
199                 {
200                         entity e = NULL;
201                         while( (e = findentity(e, owner, minigame)) )
202                         if(e.classname == "minigame_board_piece")
203                         {
204                                 if(e.netname) { strunzone(e.netname); }
205                                 delete(e);
206                         }
207                         return false;
208                 }
209                 case "join":
210                 {
211                         int pl_num = minigame_count_players(minigame);
212
213                         // Don't allow more than 2 players
214                         if(pl_num >= C4_TEAMS) { return false; }
215
216                         // Get the right team
217                         if(minigame.minigame_players)
218                                 return minigame_next_team(minigame.minigame_players.team, C4_TEAMS);
219
220                         // Team 1 by default
221                         return 1;
222                 }
223                 case "cmd":
224                 {
225                         switch(argv(0))
226                         {
227                                 case "move":
228                                         c4_move(minigame, ...(0,entity), ...(1,int) == 2 ? argv(1) : string_null );
229                                         return true;
230                         }
231
232                         return false;
233                 }
234         }
235
236         return false;
237 }
238
239
240 #elif defined(CSQC)
241
242 string c4_curr_pos; // identifier of the tile under the mouse
243 vector c4_boardpos; // HUD board position
244 vector c4_boardsize;// HUD board size
245 .int c4_checkwin; // Used to optimize checks to display a win
246
247 // Required function, draw the game board
248 void c4_hud_board(vector pos, vector mySize)
249 {
250         minigame_hud_fitsqare(pos, mySize);
251         c4_boardpos = pos;
252         c4_boardsize = mySize;
253
254         minigame_hud_simpleboard(pos,mySize,minigame_texture("c4/board_under"));
255
256         drawpic(pos, minigame_texture("c4/board_over"), mySize, '1 1 1', 1, 0);
257
258         vector tile_size = minigame_hud_denormalize_size('1 1 0' / C4_TILE_SIZE,pos,mySize);
259         vector tile_pos;
260
261         if ( (active_minigame.minigame_flags & C4_TURN_TEAM) == minigame_self.team )
262         if ( c4_valid_tile(c4_curr_pos) )
263         {
264                 tile_pos = minigame_tile_pos(c4_curr_pos,C4_NUM_CNT,C4_LET_CNT);
265                 tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
266                 minigame_drawpic_centered( tile_pos,
267                                 minigame_texture(strcat("c4/piece",ftos(minigame_self.team))),
268                                 tile_size, '1 1 1', panel_fg_alpha/2, DRAWFLAG_NORMAL );
269         }
270
271         entity e;
272         FOREACH_MINIGAME_ENTITY(e)
273         {
274                 if ( e.classname == "minigame_board_piece" )
275                 {
276                         tile_pos = minigame_tile_pos(e.netname,C4_NUM_CNT,C4_LET_CNT);
277                         tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
278
279                         if ( active_minigame.minigame_flags & C4_TURN_WIN )
280                         if ( !e.c4_checkwin )
281                                 e.c4_checkwin = c4_winning_piece(e) ? 1 : -1;
282
283                         float icon_color = 1;
284                         if ( e.c4_checkwin == -1 )
285                                 icon_color = 0.4;
286                         else if ( e.c4_checkwin == 1 )
287                         {
288                                 icon_color = 2;
289                                 minigame_drawpic_centered( tile_pos, minigame_texture("c4/winglow"),
290                                                 tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_ADDITIVE );
291                         }
292
293                         minigame_drawpic_centered( tile_pos,
294                                         minigame_texture(strcat("c4/piece",ftos(e.team))),
295                                         tile_size, '1 1 1'*icon_color, panel_fg_alpha, DRAWFLAG_NORMAL );
296                 }
297         }
298
299         if ( active_minigame.minigame_flags & C4_TURN_WIN )
300         {
301                 vector winfs = hud_fontsize*2;
302                 string playername = "";
303                 FOREACH_MINIGAME_ENTITY(e)
304                         if ( e.classname == "minigame_player" &&
305                                         e.team == (active_minigame.minigame_flags & C4_TURN_TEAM) )
306                                 playername = entcs_GetName(e.minigame_playerslot-1);
307
308                 vector win_pos = pos+eY*(mySize_y-winfs_y)/2;
309                 vector win_sz;
310                 win_sz = minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos,
311                         sprintf("%s^7 won the game!",playername),
312                         winfs, 0, DRAWFLAG_NORMAL, 0.5);
313
314                 drawfill(win_pos-eY*hud_fontsize_y,win_sz+2*eY*hud_fontsize_y,'1 1 1',0.5,DRAWFLAG_ADDITIVE);
315
316                 minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos,
317                         sprintf("%s^7 won the game!",playername),
318                         winfs, panel_fg_alpha, DRAWFLAG_NORMAL, 0.5);
319         }
320 }
321
322
323 // Required function, draw the game status panel
324 void c4_hud_status(vector pos, vector mySize)
325 {
326         HUD_Panel_DrawBg();
327         vector ts;
328         ts = minigame_drawstring_wrapped(mySize_x,pos,active_minigame.descriptor.message,
329                 hud_fontsize * 2, '0.25 0.47 0.72', panel_fg_alpha, DRAWFLAG_NORMAL,0.5);
330
331         pos_y += ts_y;
332         mySize_y -= ts_y;
333
334         vector player_fontsize = hud_fontsize * 1.75;
335         ts_y = ( mySize_y - 2*player_fontsize_y ) / 2;
336         ts_x = mySize_x;
337         vector mypos;
338         vector tile_size = '48 48 0';
339
340         mypos = pos;
341         if ( (active_minigame.minigame_flags&C4_TURN_TEAM) == 2 )
342                 mypos_y  += player_fontsize_y + ts_y;
343         drawfill(mypos,eX*mySize_x+eY*player_fontsize_y,'1 1 1',0.5,DRAWFLAG_ADDITIVE);
344         mypos_y += player_fontsize_y;
345         drawfill(mypos,eX*mySize_x+eY*tile_size_y,'1 1 1',0.25,DRAWFLAG_ADDITIVE);
346
347         entity e;
348         FOREACH_MINIGAME_ENTITY(e)
349         {
350                 if ( e.classname == "minigame_player" )
351                 {
352                         mypos = pos;
353                         if ( e.team == 2 )
354                                 mypos_y  += player_fontsize_y + ts_y;
355                         minigame_drawcolorcodedstring_trunc(mySize_x,mypos,
356                                 entcs_GetName(e.minigame_playerslot-1),
357                                 player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
358
359                         mypos_y += player_fontsize_y;
360                         drawpic( mypos,
361                                         minigame_texture(strcat("c4/piece",ftos(e.team))),
362                                         tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
363
364                         mypos_x += tile_size_x;
365                 }
366         }
367 }
368
369 // Turn a set of flags into a help message
370 string c4_turn_to_string(int turnflags)
371 {
372         if ( turnflags & C4_TURN_DRAW )
373                 return _("Draw");
374
375         if ( turnflags & C4_TURN_WIN )
376         {
377                 if ( (turnflags&C4_TURN_TEAM) != minigame_self.team )
378                         return _("You lost the game!");
379                 return _("You win!");
380         }
381
382         if ( (turnflags & C4_TURN_TEAM) != minigame_self.team )
383                 return _("Wait for your opponent to make their move");
384
385         if ( turnflags & C4_TURN_PLACE )
386                 return _("Click on the game board to place your piece");
387
388         return "";
389 }
390
391 // Make the correct move
392 void c4_make_move(entity minigame)
393 {
394         if ( minigame.minigame_flags == (C4_TURN_PLACE|minigame_self.team) )
395         {
396                 minigame_cmd("move ",c4_curr_pos);
397         }
398 }
399
400 void c4_set_curr_pos(string s)
401 {
402         if ( c4_curr_pos )
403                 strunzone(c4_curr_pos);
404         if ( s )
405                 s = strzone(s);
406         c4_curr_pos = s;
407 }
408
409 // Required function, handle client events
410 int c4_client_event(entity minigame, string event, ...)
411 {
412         switch(event)
413         {
414                 case "activate":
415                 {
416                         c4_set_curr_pos("");
417                         minigame.message = c4_turn_to_string(minigame.minigame_flags);
418                         return false;
419                 }
420                 case "key_pressed":
421                 {
422                         if((minigame.minigame_flags & C4_TURN_TEAM) == minigame_self.team)
423                         {
424                                 switch ( ...(0,int) )
425                                 {
426                                         case K_RIGHTARROW:
427                                         case K_KP_RIGHTARROW:
428                                                 if ( ! c4_curr_pos )
429                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, "a3"));
430                                                 else
431                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_relative_tile(c4_curr_pos,1,0,C4_NUM_CNT,C4_LET_CNT)));
432                                                 return true;
433                                         case K_LEFTARROW:
434                                         case K_KP_LEFTARROW:
435                                                 if ( ! c4_curr_pos )
436                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, "c3"));
437                                                 else
438                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_relative_tile(c4_curr_pos,-1,0,C4_NUM_CNT,C4_LET_CNT)));
439                                                 return true;
440                                         /*case K_UPARROW:
441                                         case K_KP_UPARROW:
442                                                 if ( ! c4_curr_pos )
443                                                         c4_set_curr_pos("a1");
444                                                 else
445                                                         c4_set_curr_pos(minigame_relative_tile(c4_curr_pos,0,1,6,7));
446                                                 return true;
447                                         case K_DOWNARROW:
448                                         case K_KP_DOWNARROW:
449                                                 if ( ! c4_curr_pos )
450                                                         c4_set_curr_pos("a3");
451                                                 else
452                                                         c4_set_curr_pos(minigame_relative_tile(c4_curr_pos,0,-1,6,7));
453                                                 return true;*/
454                                         case K_ENTER:
455                                         case K_KP_ENTER:
456                                         case K_SPACE:
457                                                 c4_make_move(minigame);
458                                                 return true;
459                                 }
460                         }
461
462                         return false;
463                 }
464                 case "mouse_pressed":
465                 {
466                         if(...(0,int) == K_MOUSE1)
467                         {
468                                 c4_make_move(minigame);
469                                 return true;
470                         }
471
472                         return false;
473                 }
474                 case "mouse_moved":
475                 {
476                         vector mouse_pos = minigame_hud_normalize(mousepos,c4_boardpos,c4_boardsize);
477                         if ( minigame.minigame_flags == (C4_TURN_PLACE|minigame_self.team) )
478                         {
479                                 c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_tile_name(mouse_pos,C4_NUM_CNT,C4_LET_CNT)));
480                         }
481                         if ( ! c4_valid_tile(c4_curr_pos) )
482                                 c4_set_curr_pos("");
483
484                         return true;
485                 }
486                 case "network_receive":
487                 {
488                         entity sent = ...(0,entity);
489                         int sf = ...(1,int);
490                         if ( sent.classname == "minigame" )
491                         {
492                                 if ( sf & MINIG_SF_UPDATE )
493                                 {
494                                         sent.message = c4_turn_to_string(sent.minigame_flags);
495                                         if ( sent.minigame_flags & minigame_self.team )
496                                                 minigame_prompt();
497                                 }
498                         }
499
500                         return false;
501                 }
502         }
503
504         return false;
505 }
506
507 #endif