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