3 REGISTER_MINIGAME(nmm, _("Nine Men's Morris"));
5 const int NMM_TURN_PLACE = 0x0100; // player has to place a piece on the board
6 const int NMM_TURN_MOVE = 0x0200; // player has to move a piece by one tile
7 const int NMM_TURN_FLY = 0x0400; // player has to move a piece anywhere
8 const int NMM_TURN_TAKE = 0x0800; // player has to take a non-mill piece
9 const int NMM_TURN_TAKEANY=0x1000; // combine with NMM_TURN_TAKE, can take mill pieces
10 const int NMM_TURN_WIN = 0x2000; // player has won
11 const int NMM_TURN_TYPE = 0xff00;
12 const int NMM_TURN_TEAM1 = 0x0001;
13 const int NMM_TURN_TEAM2 = 0x0002;
14 const int NMM_TURN_TEAM = 0x00ff;
16 const int NMM_PIECE_DEAD = 0x0; // captured by the enemy
17 const int NMM_PIECE_HOME = 0x1; // not yet placed
18 const int NMM_PIECE_BOARD = 0x2; // placed on the board
20 const int NMM_SPECTATOR_TEAM = 255; // must be above max teams and equal to or below 255
22 .int nmm_tile_distance;
23 .entity nmm_tile_piece;
24 .string nmm_tile_hmill;
25 .string nmm_tile_vmill;
27 // build a string containing the indices of the tile to check for a horizontal mill
28 string nmm_tile_build_hmill(entity tile)
30 int number = minigame_tile_number(tile.netname);
31 int letter = minigame_tile_letter(tile.netname);
32 if ( number == letter || number+letter == 6 )
34 int add = letter < 3 ? 1 : -1;
35 return strcat(tile.netname," ",
36 minigame_tile_buildname(letter+add*tile.nmm_tile_distance,number)," ",
37 minigame_tile_buildname(letter+add*2*tile.nmm_tile_distance,number) );
39 else if ( letter == 3 )
40 return strcat(minigame_tile_buildname(letter-tile.nmm_tile_distance,number)," ",
42 minigame_tile_buildname(letter+tile.nmm_tile_distance,number) );
43 else if ( letter < 3 )
44 return strcat(minigame_tile_buildname(0,number)," ",
45 minigame_tile_buildname(1,number)," ",
46 minigame_tile_buildname(2,number) );
48 return strcat(minigame_tile_buildname(4,number)," ",
49 minigame_tile_buildname(5,number)," ",
50 minigame_tile_buildname(6,number) );
53 // build a string containing the indices of the tile to check for a vertical mill
54 string nmm_tile_build_vmill(entity tile)
56 int letter = minigame_tile_letter(tile.netname);
57 int number = minigame_tile_number(tile.netname);
58 if ( letter == number || letter+number == 6 )
60 int add = number < 3 ? 1 : -1;
61 return strcat(tile.netname," ",
62 minigame_tile_buildname(letter,number+add*tile.nmm_tile_distance)," ",
63 minigame_tile_buildname(letter,number+add*2*tile.nmm_tile_distance) );
65 else if ( number == 3 )
66 return strcat(minigame_tile_buildname(letter,number-tile.nmm_tile_distance)," ",
68 minigame_tile_buildname(letter,number+tile.nmm_tile_distance) );
69 else if ( number < 3 )
70 return strcat(minigame_tile_buildname(letter,0)," ",
71 minigame_tile_buildname(letter,1)," ",
72 minigame_tile_buildname(letter,2) );
74 return strcat(minigame_tile_buildname(letter,4)," ",
75 minigame_tile_buildname(letter,5)," ",
76 minigame_tile_buildname(letter,6) );
80 // \param id Tile index (eg: a1)
81 // \param minig Owner minigame instance
82 // \param distance Distance from adjacent tiles
83 void nmm_spawn_tile(string id, entity minig, int distance)
85 // TODO global variable + list_next for simpler tile loops
86 entity e = new(minigame_nmm_tile);
87 e.origin = minigame_tile_pos(id,7,7);
91 e.nmm_tile_distance = distance;
92 e.nmm_tile_hmill = strzone(nmm_tile_build_hmill(e));
93 e.nmm_tile_vmill = strzone(nmm_tile_build_vmill(e));
96 // Create a tile square and recursively create inner squares
97 // \param minig Owner minigame instance
98 // \param offset Index offset (eg: 1 to start the square at b2, 0 at a1 etc.)
99 // \param skip Number of indices to skip between tiles (eg 1: a1, a3)
100 void nmm_spawn_tile_square( entity minig, int offset, int skip )
104 for ( int i = 0; i < 3; ++i )
107 for ( int j = 0; j < 3; ++j )
109 if ( i != 1 || j != 1 )
110 nmm_spawn_tile(strzone(minigame_tile_buildname(letter,number)),minig, skip+1);
117 nmm_spawn_tile_square(minig,offset+1,skip-1);
120 // Remove tiles of a NMM minigame
121 void nmm_kill_tiles(entity minig)
124 while ( ( e = findentity(e,owner,minig) ) )
125 if ( e.classname == "minigame_nmm_tile" )
128 strfree(e.nmm_tile_hmill);
129 strfree(e.nmm_tile_vmill);
134 // Create the tiles of a NMM minigame
135 void nmm_init_tiles(entity minig)
137 nmm_spawn_tile_square(minig,0,2);
140 // Find a tile by its id
141 entity nmm_find_tile(entity minig, string id)
144 while ( ( e = findentity(e,owner,minig) ) )
145 if ( e.classname == "minigame_nmm_tile" && e.netname == id )
150 // Check whether two tiles are adjacent
151 bool nmm_tile_adjacent(entity tile1, entity tile2)
154 int dnumber = fabs ( minigame_tile_number(tile1.netname) - minigame_tile_number(tile2.netname) );
155 int dletter = fabs ( minigame_tile_letter(tile1.netname) - minigame_tile_letter(tile2.netname) );
157 return ( dnumber == 0 && ( dletter == 1 || dletter == tile1.nmm_tile_distance ) ) ||
158 ( dletter == 0 && ( dnumber == 1 || dnumber == tile1.nmm_tile_distance ) );
161 // Returns 1 if there is at least 1 free adjacent tile
162 bool nmm_tile_canmove(entity tile)
165 while ( ( e = findentity(e,owner,tile.owner) ) )
166 if ( e.classname == "minigame_nmm_tile" && !e.nmm_tile_piece
167 && nmm_tile_adjacent(e,tile) )
174 // Check if the given tile id appears in the string
175 bool nmm_in_mill_string(entity tile, string s)
177 int argc = tokenize(s);
178 for ( int i = 0; i < argc; ++i )
180 entity e = nmm_find_tile(tile.owner,argv(i));
181 if ( !e || !e.nmm_tile_piece || e.nmm_tile_piece.team != tile.nmm_tile_piece.team )
187 // Check if a tile is in a mill
188 bool nmm_in_mill(entity tile)
190 return tile.nmm_tile_piece && (
191 nmm_in_mill_string(tile,tile.nmm_tile_hmill) ||
192 nmm_in_mill_string(tile,tile.nmm_tile_vmill) );
197 // Find a NMM piece matching some of the given flags and team number
198 entity nmm_find_piece(entity start, entity minigame, int teamn, int pieceflags)
201 while ( ( e = findentity(e,owner,minigame) ) )
202 if ( e.classname == "minigame_board_piece" &&
203 (e.minigame_flags & pieceflags) && e.team == teamn )
208 // Count NMM pieces matching flags and team number
209 int nmm_count_pieces(entity minigame, int teamn, int pieceflags)
213 while (( e = nmm_find_piece(e,minigame, teamn, pieceflags) ))
218 // required function, handle server side events
219 int nmm_server_event(entity minigame, string event, ...)
221 if ( event == "start" )
223 minigame.minigame_flags = NMM_TURN_PLACE|NMM_TURN_TEAM1;
224 nmm_init_tiles(minigame);
226 for ( int i = 0; i < 7; ++i )
228 e = msle_spawn(minigame,new(minigame_board_piece));
230 e.minigame_flags = NMM_PIECE_HOME;
231 e = msle_spawn(minigame,new(minigame_board_piece));
233 e.minigame_flags = NMM_PIECE_HOME;
238 else if ( event == "end" )
240 nmm_kill_tiles(minigame);
242 else if ( event == "join" )
246 for ( e = minigame.minigame_players; e; e = e.list_next )
249 return NMM_SPECTATOR_TEAM;
250 if ( minigame.minigame_players && minigame.minigame_players.team == 1 )
254 else if ( event == "cmd" )
256 entity e = ...(0,entity);
257 int argc = ...(1,int);
260 bool move_ok = false;
262 if ( e && argc >= 2 && argv(0) == "move" &&
263 ( minigame.minigame_flags & NMM_TURN_TEAM ) == e.team )
265 tile = nmm_find_tile(minigame,argv(1));
270 else if ( minigame.minigame_flags & NMM_TURN_PLACE )
272 piece = nmm_find_piece(NULL,minigame,e.team,NMM_PIECE_HOME);
273 if ( !tile.nmm_tile_piece && piece )
275 tile.nmm_tile_piece = piece;
276 piece.minigame_flags = NMM_PIECE_BOARD;
277 piece.origin = tile.origin;
278 piece.SendFlags |= MINIG_SF_UPDATE;
282 else if ( minigame.minigame_flags & NMM_TURN_MOVE )
284 if ( tile.nmm_tile_piece && tile.nmm_tile_piece.team == e.team )
286 piece = tile.nmm_tile_piece;
287 entity tile2 = nmm_find_tile(minigame,argv(2));
288 if ( tile2 && nmm_tile_adjacent(tile,tile2) && !tile2.nmm_tile_piece )
290 tile.nmm_tile_piece = NULL;
291 tile2.nmm_tile_piece = piece;
292 piece.origin = tile2.origin;
293 piece.SendFlags |= MINIG_SF_UPDATE;
300 else if ( minigame.minigame_flags & NMM_TURN_FLY )
302 if ( tile.nmm_tile_piece && tile.nmm_tile_piece.team == e.team )
304 piece = tile.nmm_tile_piece;
305 entity tile2 = nmm_find_tile(minigame,argv(2));
306 if ( tile2 && !tile2.nmm_tile_piece )
308 tile.nmm_tile_piece = NULL;
309 tile2.nmm_tile_piece = piece;
310 piece.origin = tile2.origin;
311 piece.SendFlags |= MINIG_SF_UPDATE;
318 else if ( minigame.minigame_flags & NMM_TURN_TAKE )
320 piece = tile.nmm_tile_piece;
321 if ( piece && piece.nmm_tile_piece.team != e.team )
323 tile.nmm_tile_piece = NULL;
324 piece.minigame_flags = NMM_PIECE_DEAD;
325 piece.SendFlags |= MINIG_SF_UPDATE;
330 int nextteam = e.team % 2 + 1;
331 int npieces = nmm_count_pieces(minigame,nextteam,NMM_PIECE_HOME|NMM_PIECE_BOARD);
335 minigame.minigame_flags = NMM_TURN_WIN | e.team;
336 minigame.SendFlags |= MINIG_SF_UPDATE;
340 if ( !(minigame.minigame_flags & NMM_TURN_TAKE) && nmm_in_mill(tile) )
342 minigame.minigame_flags = NMM_TURN_TAKE|e.team;
343 int takemill = NMM_TURN_TAKEANY;
345 while ( ( f = findentity(f,owner,minigame) ) )
346 if ( f.classname == "minigame_nmm_tile" && f.nmm_tile_piece &&
347 f.nmm_tile_piece.team == nextteam && !nmm_in_mill(f) )
352 minigame.minigame_flags |= takemill;
356 if ( nmm_find_piece(NULL,minigame,nextteam,NMM_PIECE_HOME) )
357 minigame.minigame_flags = NMM_TURN_PLACE|nextteam;
358 else if ( npieces == 3 )
359 minigame.minigame_flags = NMM_TURN_FLY|nextteam;
362 minigame.minigame_flags = NMM_TURN_WIN|e.team;
364 while ( ( f = findentity(f,owner,minigame) ) )
365 if ( f.classname == "minigame_nmm_tile" && f.nmm_tile_piece &&
366 f.nmm_tile_piece.team == nextteam && nmm_tile_canmove(f) )
368 minigame.minigame_flags = NMM_TURN_MOVE|nextteam;
373 minigame.SendFlags |= MINIG_SF_UPDATE;
376 LOG_TRACE("Invalid move: ", ...(2, string));
389 vector nmm_boardsize;
391 // whether the given tile is a valid selection
392 bool nmm_valid_selection(entity tile)
394 if ( ( tile.owner.minigame_flags & NMM_TURN_TEAM ) != minigame_self.team )
395 return false; // not our turn
396 if ( tile.owner.minigame_flags & NMM_TURN_PLACE )
397 return !tile.nmm_tile_piece; // need to put a piece on an empty spot
398 if ( tile.owner.minigame_flags & NMM_TURN_MOVE )
400 if ( tile.nmm_tile_piece && tile.nmm_tile_piece.team == minigame_self.team &&
401 nmm_tile_canmove(tile) )
402 return true; // movable tile
403 if ( nmm_fromtile ) // valid destination
404 return !tile.nmm_tile_piece && nmm_tile_adjacent(nmm_fromtile,tile);
407 if ( tile.owner.minigame_flags & NMM_TURN_FLY )
410 return !tile.nmm_tile_piece;
412 return tile.nmm_tile_piece && tile.nmm_tile_piece.team == minigame_self.team;
414 if ( tile.owner.minigame_flags & NMM_TURN_TAKE )
415 return tile.nmm_tile_piece && tile.nmm_tile_piece.team != minigame_self.team &&
416 ( (tile.owner.minigame_flags & NMM_TURN_TAKEANY) || !nmm_in_mill(tile) );
420 // whether it should highlight valid tile selections
421 bool nmm_draw_avaliable(entity tile)
423 if ( ( tile.owner.minigame_flags & NMM_TURN_TEAM ) != minigame_self.team )
425 if ( (tile.owner.minigame_flags & NMM_TURN_TAKE) )
427 if ( (tile.owner.minigame_flags & (NMM_TURN_FLY|NMM_TURN_MOVE)) && nmm_fromtile )
428 return !tile.nmm_tile_piece;
432 // Required function, draw the game board
433 void nmm_hud_board(vector pos, vector mySize)
435 minigame_hud_fitsqare(pos, mySize);
437 nmm_boardsize = mySize;
438 minigame_hud_simpleboard(pos,mySize,minigame_texture("nmm/board"));
440 vector tile_size = minigame_hud_denormalize_size('1 1 0'/7,pos,mySize);
443 FOREACH_MINIGAME_ENTITY(e)
445 if ( e.classname == "minigame_nmm_tile" )
447 tile_pos = minigame_hud_denormalize(e.origin,pos,mySize);
449 if ( e == nmm_fromtile )
451 minigame_drawpic_centered( tile_pos, minigame_texture("nmm/tile_active"),
452 tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
454 else if ( nmm_draw_avaliable(e) && nmm_valid_selection(e) )
456 minigame_drawpic_centered( tile_pos, minigame_texture("nmm/tile_available"),
457 tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
460 if ( e == nmm_currtile )
462 minigame_drawpic_centered( tile_pos, minigame_texture("nmm/tile_selected"),
463 tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_ADDITIVE );
466 if ( e.nmm_tile_piece )
468 minigame_drawpic_centered( tile_pos,
469 minigame_texture(strcat("nmm/piece",ftos(e.nmm_tile_piece.team))),
470 tile_size*0.8, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
473 //drawstring(tile_pos, e.netname, hud_fontsize, '1 0 0', 1, DRAWFLAG_NORMAL);
477 if ( active_minigame.minigame_flags & NMM_TURN_WIN )
479 vector winfs = hud_fontsize*2;
481 FOREACH_MINIGAME_ENTITY(e)
482 if ( e.classname == "minigame_player" &&
483 e.team == (active_minigame.minigame_flags & NMM_TURN_TEAM) )
484 pname = entcs_GetName(e.minigame_playerslot-1);
486 vector win_pos = pos+eY*(mySize_y-winfs_y)/2;
488 win_sz = minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos,
489 sprintf(_("%s^7 won the game!"), pname),
490 winfs, 0, DRAWFLAG_NORMAL, 0.5);
492 drawfill(win_pos-eY*hud_fontsize_y,win_sz+2*eY*hud_fontsize_y,'1 1 1',0.5*panel_fg_alpha,DRAWFLAG_ADDITIVE);
494 minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos,
495 sprintf(_("%s^7 won the game!"), pname),
496 winfs, panel_fg_alpha, DRAWFLAG_NORMAL, 0.5);
500 // Required function, draw the game status panel
501 void nmm_hud_status(vector pos, vector mySize)
505 ts = minigame_drawstring_wrapped(mySize_x,pos,active_minigame.descriptor.message,
506 hud_fontsize * 2, '0.25 0.47 0.72', panel_fg_alpha, DRAWFLAG_NORMAL,0.5);
510 vector player_fontsize = hud_fontsize * 1.75;
511 ts_y = ( mySize_y - 2*player_fontsize_y ) / 2;
516 vector piece_sz = '48 48 0';
517 float piece_space = piece_sz_x + ( ts_x - 7 * piece_sz_x ) / 6;
519 float piece_light = 1;
522 if(minigame_self.team != NMM_SPECTATOR_TEAM)
525 if ( (active_minigame.minigame_flags&NMM_TURN_TEAM) == 2 )
526 mypos_y += player_fontsize_y + ts_y;
527 drawfill(mypos,eX*mySize_x+eY*player_fontsize_y,'1 1 1',0.5*panel_fg_alpha,DRAWFLAG_ADDITIVE);
528 mypos_y += player_fontsize_y;
529 drawfill(mypos,eX*mySize_x+eY*piece_sz_y,'1 1 1',0.25*panel_fg_alpha,DRAWFLAG_ADDITIVE);
532 FOREACH_MINIGAME_ENTITY(e)
534 if ( e.classname == "minigame_player" && e.team != NMM_SPECTATOR_TEAM )
538 mypos_y += player_fontsize_y + ts_y;
539 minigame_drawcolorcodedstring_trunc(mySize_x,mypos,
540 entcs_GetName(e.minigame_playerslot-1),
541 player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
543 else if ( e.classname == "minigame_board_piece" )
546 mypos_y += player_fontsize_y;
550 player2x += piece_space;
551 mypos_y += player_fontsize_y + ts_y;
556 player1x += piece_space;
558 if ( e.minigame_flags == NMM_PIECE_HOME )
560 else if ( e.minigame_flags == NMM_PIECE_BOARD )
565 drawpic(mypos, minigame_texture(strcat("nmm/piece",ftos(e.team))), piece_sz,
566 '1 1 1'*piece_light, panel_fg_alpha, DRAWFLAG_NORMAL );
571 // Make the correct move
572 void nmm_make_move(entity minigame)
576 if ( minigame.minigame_flags & (NMM_TURN_PLACE|NMM_TURN_TAKE) )
578 minigame_cmd("move ",nmm_currtile.netname);
581 else if ( (minigame.minigame_flags & (NMM_TURN_MOVE|NMM_TURN_FLY)) )
583 if ( nmm_fromtile == nmm_currtile )
587 else if ( nmm_currtile.nmm_tile_piece && nmm_currtile.nmm_tile_piece.team == minigame_self.team )
589 nmm_fromtile = nmm_currtile;
591 else if ( nmm_fromtile )
593 minigame_cmd("move ",nmm_fromtile.netname," ",nmm_currtile.netname);
602 string nmm_turn_to_string(int turnflags)
604 if( minigame_self.team == NMM_SPECTATOR_TEAM )
605 return _("You are spectating");
607 if ( turnflags & NMM_TURN_WIN )
609 if ( (turnflags&NMM_TURN_TEAM) != minigame_self.team )
610 return _("You lost the game!");
611 return _("You win!");
614 if ( (turnflags&NMM_TURN_TEAM) != minigame_self.team )
615 return _("Wait for your opponent to make their move");
616 if ( turnflags & NMM_TURN_PLACE )
617 return _("Click on the game board to place your piece");
618 if ( turnflags & NMM_TURN_MOVE )
619 return _("You can select one of your pieces to move it in one of the surrounding places");
620 if ( turnflags & NMM_TURN_FLY )
621 return _("You can select one of your pieces to move it anywhere on the board");
622 if ( turnflags & NMM_TURN_TAKE )
623 return _("You can take one of the opponent's pieces");
628 // Required function, handle client events
629 int nmm_client_event(entity minigame, string event, ...)
631 if ( event == "activate" )
634 nmm_init_tiles(minigame);
635 strcpy(minigame.message, nmm_turn_to_string(minigame.minigame_flags));
637 else if ( event == "deactivate" )
640 nmm_kill_tiles(minigame);
641 strfree(minigame.message);
643 else if ( (event == "key_pressed" || event == "key_released") )
645 bool event_blocked = ((event == "key_released")
646 || ((minigame.minigame_flags & NMM_TURN_TEAM) != minigame_self.team));
647 if (!(minigame.minigame_flags & NMM_TURN_WIN))
649 switch ( ...(0,int) )
652 case K_KP_RIGHTARROW:
655 if ( ! nmm_currtile )
656 nmm_currtile = nmm_find_tile(active_minigame,"a7");
659 string tileid = nmm_currtile.netname;
661 while ( !nmm_currtile )
663 tileid = minigame_relative_tile(tileid,1,0,7,7);
664 nmm_currtile = nmm_find_tile(active_minigame,tileid);
672 if ( ! nmm_currtile )
673 nmm_currtile = nmm_find_tile(active_minigame,"g7");
676 string tileid = nmm_currtile.netname;
678 while ( !nmm_currtile )
680 tileid = minigame_relative_tile(tileid,-1,0,7,7);
681 nmm_currtile = nmm_find_tile(active_minigame,tileid);
689 if ( ! nmm_currtile )
690 nmm_currtile = nmm_find_tile(active_minigame,"a1");
693 string tileid = nmm_currtile.netname;
695 while ( !nmm_currtile )
697 tileid = minigame_relative_tile(tileid,0,1,7,7);
698 nmm_currtile = nmm_find_tile(active_minigame,tileid);
706 if ( ! nmm_currtile )
707 nmm_currtile = nmm_find_tile(active_minigame,"a7");
710 string tileid = nmm_currtile.netname;
712 while ( !nmm_currtile )
714 tileid = minigame_relative_tile(tileid,0,-1,7,7);
715 nmm_currtile = nmm_find_tile(active_minigame,tileid);
724 nmm_make_move(minigame);
730 else if ( event == "mouse_pressed" && ...(0,int) == K_MOUSE1 )
732 nmm_client_event(minigame, "mouse_moved");
733 nmm_make_move(minigame);
736 else if ( event == "mouse_moved" )
740 vector tile_size = minigame_hud_denormalize_size('1 1 0'/7,nmm_boardpos,nmm_boardsize);
742 FOREACH_MINIGAME_ENTITY(e)
744 if ( e.classname == "minigame_nmm_tile" )
746 tile_pos = minigame_hud_denormalize(e.origin,nmm_boardpos,nmm_boardsize)-tile_size/2;
747 if ( minigame_hud_mouse_in(tile_pos, tile_size) && nmm_valid_selection(e) )
756 else if ( event == "network_receive" )
758 entity sent = ...(0,entity);
760 if ( sent.classname == "minigame_board_piece" && ( ...(1,int) & MINIG_SF_UPDATE ) )
764 if ( sent.minigame_flags & NMM_PIECE_BOARD )
765 tileid = minigame_tile_name(sent.origin,7,7);
766 FOREACH_MINIGAME_ENTITY(e)
768 if ( e.classname == "minigame_nmm_tile" )
770 if ( e.nmm_tile_piece == sent )
771 e.nmm_tile_piece = NULL;
772 if ( e.netname == tileid )
773 e.nmm_tile_piece = sent;
777 else if ( sent.classname == "minigame" && ( ...(1,int) & MINIG_SF_UPDATE ) )
779 strcpy(sent.message, nmm_turn_to_string(sent.minigame_flags));
780 if ( sent.minigame_flags & minigame_self.team )