]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/minigames/minigame/c4.qc
6727655bc52853836e8b081d11e11980f056ec47
[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         FOREACH_MINIGAME_ENTITY(e)
357         {
358                 if ( e.classname == "minigame_player" && e.team != C4_SPECTATOR_TEAM )
359                 {
360                         mypos = pos;
361                         if ( e.team == 2 )
362                                 mypos_y  += player_fontsize_y + ts_y;
363                         minigame_drawcolorcodedstring_trunc(mySize_x,mypos,
364                                 entcs_GetName(e.minigame_playerslot-1),
365                                 player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
366
367                         mypos_y += player_fontsize_y;
368                         drawpic( mypos,
369                                         minigame_texture(strcat("c4/piece",ftos(e.team))),
370                                         tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
371
372                         mypos_x += tile_size_x;
373                 }
374         }
375 }
376
377 // Turn a set of flags into a help message
378 string c4_turn_to_string(int turnflags)
379 {
380         if(minigame_self.team == C4_SPECTATOR_TEAM)
381                 return _("You are spectating");
382
383         if ( turnflags & C4_TURN_DRAW )
384                 return _("Draw");
385
386         if ( turnflags & C4_TURN_WIN )
387         {
388                 if ( (turnflags&C4_TURN_TEAM) != minigame_self.team )
389                         return _("You lost the game!");
390                 return _("You win!");
391         }
392
393         if ( (turnflags & C4_TURN_TEAM) != minigame_self.team )
394                 return _("Wait for your opponent to make their move");
395
396         if ( turnflags & C4_TURN_PLACE )
397                 return _("Click on the game board to place your piece");
398
399         return "";
400 }
401
402 // Make the correct move
403 void c4_make_move(entity minigame)
404 {
405         if ( minigame.minigame_flags == (C4_TURN_PLACE|minigame_self.team) )
406         {
407                 minigame_cmd("move ",c4_curr_pos);
408         }
409 }
410
411 void c4_set_curr_pos(string s)
412 {
413         strfree(c4_curr_pos);
414         if ( s )
415                 s = strzone(s);
416         c4_curr_pos = s;
417 }
418
419 // Required function, handle client events
420 int c4_client_event(entity minigame, string event, ...)
421 {
422         switch(event)
423         {
424                 case "activate":
425                 {
426                         c4_set_curr_pos("");
427                         strcpy(minigame.message, c4_turn_to_string(minigame.minigame_flags));
428                         return false;
429                 }
430                 case "deactivate":
431                 {
432                         strfree(minigame.message);
433                         return false;
434                 }
435                 case "key_pressed":
436                 case "key_released":
437                 {
438                         bool event_blocked = ((event == "key_released")
439                                 || ((minigame.minigame_flags & C4_TURN_TEAM) != minigame_self.team));
440                         if (!(minigame.minigame_flags & C4_TURN_WIN) && !(minigame.minigame_flags & C4_TURN_DRAW))
441                         {
442                                 switch ( ...(0,int) )
443                                 {
444                                         case K_RIGHTARROW:
445                                         case K_KP_RIGHTARROW:
446                                                 if (event_blocked)
447                                                         return true;
448                                                 if ( ! c4_curr_pos )
449                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, "a3"));
450                                                 else
451                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_relative_tile(c4_curr_pos,1,0,C4_NUM_CNT,C4_LET_CNT)));
452                                                 return true;
453                                         case K_LEFTARROW:
454                                         case K_KP_LEFTARROW:
455                                                 if (event_blocked)
456                                                         return true;
457                                                 if ( ! c4_curr_pos )
458                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, "c3"));
459                                                 else
460                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_relative_tile(c4_curr_pos,-1,0,C4_NUM_CNT,C4_LET_CNT)));
461                                                 return true;
462                                         case K_UPARROW:
463                                         case K_KP_UPARROW:
464                                         case K_DOWNARROW:
465                                         case K_KP_DOWNARROW:
466                                                 return true;
467                                         /*case K_UPARROW:
468                                         case K_KP_UPARROW:
469                                                 if (event_blocked)
470                                                         return true;
471                                                 if ( ! c4_curr_pos )
472                                                         c4_set_curr_pos("a1");
473                                                 else
474                                                         c4_set_curr_pos(minigame_relative_tile(c4_curr_pos,0,1,6,7));
475                                                 return true;
476                                         case K_DOWNARROW:
477                                         case K_KP_DOWNARROW:
478                                                 if (event_blocked)
479                                                         return true;
480                                                 if ( ! c4_curr_pos )
481                                                         c4_set_curr_pos("a3");
482                                                 else
483                                                         c4_set_curr_pos(minigame_relative_tile(c4_curr_pos,0,-1,6,7));
484                                                 return true;*/
485                                         case K_ENTER:
486                                         case K_KP_ENTER:
487                                         case K_SPACE:
488                                                 if (event_blocked)
489                                                         return true;
490                                                 c4_make_move(minigame);
491                                                 return true;
492                                 }
493                         }
494
495                         return false;
496                 }
497                 case "mouse_pressed":
498                 {
499                         if(...(0,int) == K_MOUSE1)
500                         {
501                                 c4_client_event(minigame, "mouse_moved");
502                                 c4_make_move(minigame);
503                                 return true;
504                         }
505
506                         return false;
507                 }
508                 case "mouse_moved":
509                 {
510                         vector mouse_pos = minigame_hud_normalize(mousepos,c4_boardpos,c4_boardsize);
511                         if ( minigame.minigame_flags == (C4_TURN_PLACE|minigame_self.team) )
512                         {
513                                 c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_tile_name(mouse_pos,C4_NUM_CNT,C4_LET_CNT)));
514                         }
515                         if ( ! c4_valid_tile(c4_curr_pos) )
516                                 c4_set_curr_pos("");
517
518                         return true;
519                 }
520                 case "network_receive":
521                 {
522                         entity sent = ...(0,entity);
523                         int sf = ...(1,int);
524                         if ( sent.classname == "minigame" )
525                         {
526                                 if ( sf & MINIG_SF_UPDATE )
527                                 {
528                                         strcpy(sent.message, c4_turn_to_string(sent.minigame_flags));
529                                         if ( sent.minigame_flags & minigame_self.team )
530                                                 minigame_prompt();
531                                 }
532                         }
533
534                         return false;
535                 }
536         }
537
538         return false;
539 }
540
541 #endif