public LocationType GetLocationAtCoord(CoordAbs c) => LocationsLayout.TryIndex(c.X, c.Y); // can return null /// <summary> /// Returns the players that are still in the game, checking each by the "active" requirements /// </summary> /// <param name="rules"></param> /// <param name="board"></param> /// <returns></returns> public List <Player> TerminalStateCheck(GameRules rules) // uses the custom TerminalStateFunction on each player { List <Player> activePlayers = new List <Player>(rules.PlayerOrder); foreach (Player p in rules.PlayerOrder) { // remove a player that no longer satisfies the "active" requirements var playerResult = rules.TerminalStateFunction(rules, this, p); if (playerResult == true) // instant win { return new List <Player>() { p } } ; else if (playerResult == false) // removed from game { activePlayers.Remove(p); } else { continue; // still in game if null } } return(activePlayers); }
public GameRules.MoveOutcomes applyMove(Move m, GameRules rules) // move must be legal { // apply the movement m.movingPiece.pos = m.ToCoord; this.PiecesLayout[m.FromCoord.X, m.FromCoord.Y] = null; this.PiecesLayout[m.ToCoord.X, m.ToCoord.Y] = null; // using a single battle function allows for easier customization // if a battle took place at all, reveal the pieces if (m.movingPiece != null && m.opponentPiece != null) { m.movingPiece.turnRevealed = TurnNumber; } if (m.opponentPiece != null) { m.opponentPiece.turnRevealed = TurnNumber; } m.outcome = rules.BattleFunction(m.movingPiece, m.opponentPiece); switch (m.outcome) { case GameRules.MoveOutcomes.Move: this.PiecesLayout[m.ToCoord.X, m.ToCoord.Y] = m.movingPiece; break; case GameRules.MoveOutcomes.Win: this.PiecesLayout[m.ToCoord.X, m.ToCoord.Y] = m.movingPiece; // remove the opponent m.opponentPiece.pos = Piece.removedPos; PieceSet.Remove(m.opponentPiece); break; case GameRules.MoveOutcomes.Lose: this.PiecesLayout[m.ToCoord.X, m.ToCoord.Y] = m.opponentPiece; // remove our piece m.movingPiece.pos = Piece.removedPos; PieceSet.Remove(m.movingPiece); break; case GameRules.MoveOutcomes.Tie: // remove the opponent m.opponentPiece.pos = Piece.removedPos; PieceSet.Remove(m.opponentPiece); // remove our piece m.movingPiece.pos = Piece.removedPos; PieceSet.Remove(m.movingPiece); break; } return(m.outcome); }
public IEnumerable <Move> GetLegalMovesForPlayer(Player p, GameRules rules) { // todo: itd be nice to index pieces by player, but we dont persist players into projected board states foreach (Piece piece in this.PieceSet.Where(x => x.Owner == p)) { foreach (Move m in piece.GetLegalMoves(this, rules)) { yield return(m); } } }
public Game(Game othergame) { // TODO some data attributes may not be completely deep-cloned, such as players and logging settings CurrentBoard = new Board(othergame.CurrentBoard); // deep copy of the board //MoveSequence = new Dictionary<int, Move>(othergame.MoveSequence); rules = new GameRules(othergame.rules.Arsenals, new List <Player>(othergame.rules.PlayerOrder), null) { BattleFunction = othergame.rules.BattleFunction, LoggingSettings = othergame.rules.LoggingSettings, TerminalStateFunction = othergame.rules.TerminalStateFunction, MaxPhysicalTurns = othergame.rules.MaxPhysicalTurns, }; }
/// <summary> /// Wraps the piece type's check for winner and adds if the piece is revealed /// </summary> /// <param name="opponent"></param> /// <returns></returns> public GameRules.MoveOutcomes PredictWinner(Piece opponent, GameRules rules) { if (opponent == null) { return(GameRules.MoveOutcomes.Move); } if (opponent.IsRevealed) { return(rules.BattleFunction(this, opponent)); } // TODO CHANGE winner determiniation function according to controller // according to minimax rules, if we dont know we should assume a loss return(GameRules.MoveOutcomes.Lose); }
public IEnumerable <Move> GetLegalMoves(Board currentBoard, GameRules rules) { foreach (CoordRel newCoord in GetPossibleMovement(currentBoard)) { // generate the move and validate it Move m = new Move(this, newCoord, currentBoard, rules); if (m.Legal) { if (rules.LoggingSettings.showEachPlayersPlanning) { Console.WriteLine("possible move: " + m.ToString()); } yield return(m); } } }
public static Game tinyStratego() { LocationType _____Space = new LocationType(); GameRules rules = new GameRules( _arsenal: new List <GameRules.Arsenal>() { // min, max, and start define the range of pieces a player can place to start the game new GameRules.Arsenal(0, 1, 1, staticPlayer1, pieceTypeBomb), new GameRules.Arsenal(0, 1, 1, staticPlayer1, pieceTypeMiner), new GameRules.Arsenal(0, 1, 1, staticPlayer1, pieceTypeFlag), new GameRules.Arsenal(0, 1, 1, staticPlayer2, pieceTypeBomb), new GameRules.Arsenal(0, 1, 1, staticPlayer2, pieceTypeMiner), new GameRules.Arsenal(0, 1, 1, staticPlayer2, pieceTypeFlag), }, _players: new List <Player>() { staticPlayer1, staticPlayer2 }, _startingBoard: new Board() { Height = 3, Width = 3, LocationsLayout = new[, ] { // x, y so first row is actually the first column { _____Space, _____Space, _____Space }, { _____Space, _____Space, _____Space }, { _____Space, _____Space, _____Space }, }, PiecesLayout = new Piece[3, 3], PieceSet = new List <Piece> { /* * [ , -2 , -f ] * [ +1 , , -1 ] * [ +f , +2 , ] */ new Piece(0, 0, staticPlayer1, pieceTypeFlag), new Piece(1, 0, staticPlayer1, pieceTypeMiner), new Piece(0, 1, staticPlayer1, pieceTypeBomb), new Piece(2, 1, staticPlayer2, pieceTypeBomb), new Piece(1, 2, staticPlayer2, pieceTypeMiner), new Piece(2, 2, staticPlayer2, pieceTypeFlag) } } ) { // Function to determine if a player automatically wins (true), lost (false), or is still active in game (null) TerminalStateFunction = (theRules, board, player) => { bool? result = null; string reason = "unknown"; // over turn limit is a loss - used for turn planning if (board.TurnNumber >= theRules.MaxPhysicalTurns) { reason = "Exceeded maximum turns"; result = false; } // game is over if player lost their flag if (!board.PieceSet.Exists(x => x.Type == pieceTypeFlag && x.pos != Piece.removedPos && x.Owner == player)) { reason = "Lost flag"; result = false; } // if we lost all our movable pieces bool lostAllPieces = board.PieceSet.Where(x => x.Owner == player && x.Type.Movable) .All(x => x.pos == Piece.removedPos); if (lostAllPieces) { reason = "Lost all mobile pieces"; result = false; } if (theRules.LoggingSettings.winLossReasons) { if (result == true) { Console.WriteLine(player.FriendlyName + " won ... " + reason); } if (result == false) { Console.WriteLine(player.FriendlyName + " lost ... " + reason); } } return(result); // otherwise still in game }, BattleFunction = FullBattleFunction, }; Game game = new Game() { GameNumber = 1, rules = rules, CurrentBoard = new Board(rules.InitialBoard), }; return(game); }
public static Game FullNewStratego(Player p1, Player p2) { LocationType _____Space = new LocationType(); LocationType plyr1Space = new LocationType() { StarterPlace = p1, }; LocationType plyr2Space = new LocationType() { StarterPlace = p2, }; LocationType waterSpace = new LocationType() { Passable = false, Standable = false, }; int pieceCount = 0; List <Piece> pieces = new List <Piece>(); #region Board Setup var Arsenal = new List <GameRules.Arsenal>() { // https://en.wikipedia.org/wiki/Stratego#Pieces // min, max, and start define the range of pieces a player can place to start the game new GameRules.Arsenal(0, 1, 1, p1, pieceTypeFlag), new GameRules.Arsenal(0, 6, 6, p1, pieceTypeBomb), new GameRules.Arsenal(0, 1, 1, p1, pieceTypeSpy), new GameRules.Arsenal(0, 8, 8, p1, pieceTypeScout), new GameRules.Arsenal(0, 5, 5, p1, pieceTypeMiner), new GameRules.Arsenal(0, 4, 4, p1, pieceType4), new GameRules.Arsenal(0, 4, 4, p1, pieceType4), new GameRules.Arsenal(0, 4, 4, p1, pieceType6), new GameRules.Arsenal(0, 3, 3, p1, pieceType7), new GameRules.Arsenal(0, 2, 2, p1, pieceType8), new GameRules.Arsenal(0, 1, 1, p1, pieceType9), new GameRules.Arsenal(0, 1, 1, p1, pieceTypeM), new GameRules.Arsenal(0, 1, 1, p2, pieceTypeFlag), new GameRules.Arsenal(0, 6, 6, p2, pieceTypeBomb), new GameRules.Arsenal(0, 1, 1, p2, pieceTypeSpy), new GameRules.Arsenal(0, 8, 8, p2, pieceTypeScout), new GameRules.Arsenal(0, 5, 5, p2, pieceTypeMiner), new GameRules.Arsenal(0, 4, 4, p2, pieceType4), new GameRules.Arsenal(0, 4, 4, p2, pieceType4), new GameRules.Arsenal(0, 4, 4, p2, pieceType6), new GameRules.Arsenal(0, 3, 3, p2, pieceType7), new GameRules.Arsenal(0, 2, 2, p2, pieceType8), new GameRules.Arsenal(0, 1, 1, p2, pieceType9), new GameRules.Arsenal(0, 1, 1, p2, pieceTypeM), }; var boardlayout = new[, ] { // x, y so first row is actually the first column { plyr1Space, plyr1Space, plyr1Space, plyr1Space, _____Space, _____Space, plyr2Space, plyr2Space, plyr2Space, plyr2Space }, { plyr1Space, plyr1Space, plyr1Space, plyr1Space, _____Space, _____Space, plyr2Space, plyr2Space, plyr2Space, plyr2Space }, { plyr1Space, plyr1Space, plyr1Space, plyr1Space, waterSpace, waterSpace, plyr2Space, plyr2Space, plyr2Space, plyr2Space }, { plyr1Space, plyr1Space, plyr1Space, plyr1Space, waterSpace, waterSpace, plyr2Space, plyr2Space, plyr2Space, plyr2Space }, { plyr1Space, plyr1Space, plyr1Space, plyr1Space, _____Space, _____Space, plyr2Space, plyr2Space, plyr2Space, plyr2Space }, { plyr1Space, plyr1Space, plyr1Space, plyr1Space, _____Space, _____Space, plyr2Space, plyr2Space, plyr2Space, plyr2Space }, { plyr1Space, plyr1Space, plyr1Space, plyr1Space, waterSpace, waterSpace, plyr2Space, plyr2Space, plyr2Space, plyr2Space }, { plyr1Space, plyr1Space, plyr1Space, plyr1Space, waterSpace, waterSpace, plyr2Space, plyr2Space, plyr2Space, plyr2Space }, { plyr1Space, plyr1Space, plyr1Space, plyr1Space, _____Space, _____Space, plyr2Space, plyr2Space, plyr2Space, plyr2Space }, { plyr1Space, plyr1Space, plyr1Space, plyr1Space, _____Space, _____Space, plyr2Space, plyr2Space, plyr2Space, plyr2Space }, }; // populate the piece colleciton with the rule's number of pieces foreach (GameRules.Arsenal ars in Arsenal) { for (int i = 0; i < ars.CountStart; i++) { pieces.Add(new Piece(-1, -1, ars.Owner, ars.Type) { pos = Piece.removedPos // ensure unplaced pieces are flagged correctly }); } } Random rand = new Random(); int countPlaced = 0; // place the pieces on the board for (int x = 0; x < 10; x++) { for (int y = 0; y < 10; y++) { var unplacedPieces = pieces.Where(q => q.Owner == boardlayout[x, y].StarterPlace && q.pos.X < 0).ToList(); // if the selected board location does not belong to a player if (unplacedPieces.Count == 0) { continue; } // get a random piece from the collection that has not been placed yet Piece randomPiece = unplacedPieces.ElementAt(rand.Next(0, unplacedPieces.Count)); randomPiece.pos = new CoordAbs(x, y); countPlaced++; } } // no piece should be remaining off the board System.Diagnostics.Debug.Assert(!pieces.Any(q => q.pos.X < 0)); #endregion GameRules rules = new GameRules( _arsenal: Arsenal, _players: new List <Player>() { p1, p2 }, _startingBoard: new Board() { Height = 10, Width = 10, LocationsLayout = boardlayout, PiecesLayout = new Piece[10, 10], PieceSet = pieces, } ) { // Function to determine if a player automatically wins (true), lost (false), or is still active in game (null) TerminalStateFunction = (theRules, board, player) => { bool? result = null; string reason = "unknown"; // over turn limit is a loss - used for turn planning if (board.TurnNumber >= theRules.MaxPhysicalTurns) { reason = "Exceeded maximum turns"; result = false; } // game is over if player lost their flag if (!board.PieceSet.Exists(x => x.Type == pieceTypeFlag && x.pos != Piece.removedPos && x.Owner == player)) { reason = "Lost flag"; result = false; } // if we lost all our movable pieces bool lostAllPieces = board.PieceSet.Where(x => x.Owner == player && x.Type.Movable) .All(x => x.pos == Piece.removedPos); if (lostAllPieces) { reason = "Lost all mobile pieces"; result = false; } if (theRules.LoggingSettings.winLossReasons) { if (result == true) { Console.WriteLine(player + " won ... " + reason); } if (result == false) { Console.WriteLine(player + " lost ... " + reason); } } return(result); // otherwise still in game }, }; Game game = new Game() { GameNumber = 1, rules = rules, CurrentBoard = new Board(rules.InitialBoard), // need to ensure it makes a deep copy }; return(game); }
/// <summary> /// Given a pice and its new location, check if the move is allowed based on obstructions, limits, etc /// </summary> /// <param name="p"></param> /// <param name="targetCoord"></param> /// <param name="currentBoard"></param> private bool InitializeAndValidateMove(Piece p, CoordAbs targetCoord, Board currentBoard, GameRules rules) { FromCoord = new CoordAbs(p.pos.X, p.pos.Y); movingPiece = p; outcome = GameRules.MoveOutcomes.Unknown; opponentPiece = null; ToCoord = targetCoord; // finish populating the Move and check the legality Legal = false; // ensure the move stays on the board if (ToCoord.X >= currentBoard.Width || ToCoord.X < 0 || ToCoord.Y >= currentBoard.Height || ToCoord.Y < 0) { return(false); } opponentPiece = currentBoard.GetPieceAtCoord(ToCoord); // may be null if (opponentPiece?.Owner == movingPiece.Owner || // cannot move into owned space currentBoard.GetLocationAtCoord(ToCoord)?.Passable == false) // cannot move into obstacles { return(false); } // check if theres is an empty line to the target location if (p.Type.CanJump == false) { if (rules.LoggingSettings.debugJumpchecks) { Console.WriteLine($"Checking space between {FromCoord} to {ToCoord}..."); } int xmin = Math.Min(FromCoord.X, ToCoord.X); int xmax = Math.Max(FromCoord.X, ToCoord.X); int ymin = Math.Min(FromCoord.Y, ToCoord.Y); int ymax = Math.Max(FromCoord.Y, ToCoord.Y); for (int y = ymin; y <= ymax; y++) { for (int x = xmin; x <= xmax; x++) { // dont include the coords of FromCoord nor ToCoord, just between if (FromCoord.X == x && FromCoord.Y == y || ToCoord.X == x && ToCoord.Y == y) { continue; } // check if the path is clear of empty pieces and is traversable if (currentBoard.PiecesLayout[x, y] != null || !currentBoard.LocationsLayout[x, y].Passable) { Legal = false; return(false); } if (rules.LoggingSettings.debugJumpchecks) { Console.WriteLine($"{x} {y} is " + currentBoard.PiecesLayout[x, y] + " / " + currentBoard.LocationsLayout[x, y].Passable); } } } } // todo: is legality and battle result predictions premature at this stage? // outcome = movingPiece.PredictWinner(opponentPiece, rules);//defaults to "move" if nothing is in the way Legal = true; return(true); }
public Move(Piece p, CoordAbs targetCoord, Board currentBoard, GameRules rules) { InitializeAndValidateMove(p, targetCoord, currentBoard, rules); }
public Move(Piece p, CoordRel targetCoord, Board currentBoard, GameRules rules) { InitializeAndValidateMove(p, targetCoord.ToAbs(p.pos.X, p.pos.Y), currentBoard, rules); }