You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Trek-12/source/Trek-12/Models/Game/Game.cs

450 lines
16 KiB

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using Models.Events;
using Models.Exceptions;
using Models.Interfaces;
using Models.Rules;
namespace Models.Game
{
/// <summary>
/// The Game class represents a game session in the application.
/// It contains all the necessary properties and methods to manage a game, including the game loop, dice rolling, and use of the game rules.
/// </summary>
[DataContract]
public class Game : INotifyPropertyChanged
{
/* Persistence */
public IPersistence PersistenceManager { get; set; }
/* List for the game and persistence */
private ObservableCollection<Player> _players;
public ObservableCollection<Player> Players
{
get => _players;
set
{
_players = value;
OnPropertyChanged(nameof(Players));
}
}
private ObservableCollection<Game> _games;
public ObservableCollection<Game> Games
{
get => _games;
set
{
_games = value;
OnPropertyChanged(nameof(Games));
}
}
private ObservableCollection<Map> _maps;
public ObservableCollection<Map> Maps
{
get => _maps;
set
{
_maps = value;
OnPropertyChanged(nameof(Maps));
}
}
private ObservableCollection<BestScore> _bestScores;
public ObservableCollection<BestScore> BestScores
{
get => _bestScores;
set
{
_bestScores = value;
OnPropertyChanged(nameof(BestScores));
}
}
private bool _isRunning;
[DataMember]
public bool IsRunning
{
get => _isRunning;
private set => _isRunning = value;
}
[DataMember]
public Player? CurrentPlayer { get; private set; }
[DataMember]
public Map? UsedMap { get; private set; }
public Dice Dice1 { get; private set;}
public Dice Dice2 { get; private set; }
[DataMember]
public int Turn { get; private set; }
public Rules.Rules GameRules { get; }
// == Events ==
public event EventHandler<GameStartedEventArgs> GameStarted;
public event EventHandler<GameEndedEventArgs> GameEnded;
public event EventHandler<BoardsUpdateEventArgs> BoardUpdated;
public event EventHandler<DiceRolledEventArgs> DiceRolled;
public event EventHandler<OperationChosenEventArgs> OperationChosen;
public event EventHandler<CellChosenEventArgs> CellChosen;
public void AddPlayer(Player player)
{
Players.Add(player);
}
public void AddGame(Game game)
{
Games.Add(game);
}
public void AddMap(Map map)
{
Maps.Add(map);
}
public void AddBestScore(BestScore bestScore)
{
BestScores.Add(bestScore);
}
public bool RemovePlayer(string playerName)
{
Player player = Players.FirstOrDefault(p => p.Pseudo == playerName);
if (player == null)
{
return false;
}
Players.Remove(player);
return true;
}
public bool ModifyPlayer(string pseudo, string newpseudo)
{
foreach (var index in Players)
{
if (index.Pseudo == pseudo)
{
index.Pseudo = newpseudo;
return true;
}
}
return false;
}
public void LoadData()
{
var data = PersistenceManager.LoadData();
foreach (var player in data.Item1)
{
Players.Add(player);
}
foreach (var game in data.Item2)
{
Games.Add(game);
}
foreach (var map in data.Item3)
{
Maps.Add(map);
}
foreach (var bestScore in data.Item4)
{
BestScores.Add(bestScore);
}
}
public void SaveData() => PersistenceManager.SaveData(Players, Games, Maps, BestScores);
public Game(IPersistence persistenceManager)
{
PersistenceManager = persistenceManager;
Players = new ObservableCollection<Player>();
Games = new ObservableCollection<Game>();
Maps = new ObservableCollection<Map>();
BestScores = new ObservableCollection<BestScore>();
GameRules = new Rules.Rules();
IsRunning = false;
}
public Game()
{
Players = new ObservableCollection<Player>();
Games = new ObservableCollection<Game>();
Maps = new ObservableCollection<Map>();
BestScores = new ObservableCollection<BestScore>();
GameRules = new Rules.Rules();
UsedMap = new Map("test");
IsRunning = false;
}
/// <summary>
/// Rolls all the dice.
/// </summary>
public void RollAllDice()
{
Dice1.Roll();
Dice2.Roll();
DiceRolled?.Invoke(this, new DiceRolledEventArgs(Dice1.Value, Dice2.Value));
}
/// <summary>
/// Marks an operation as checked in the operation grid of the game.
/// </summary>
/// <param name="operation"></param>
private void MarkOperationAsChecked(Operation operation)
{
int operationIndex = (int)operation;
int operationsPerType = 4; // Chaque type d'opération peut être fait 4 fois
for (int i = operationIndex * operationsPerType; i < (operationIndex + 1) * operationsPerType; i++)
{
if (!UsedMap.OperationGrid[i].IsChecked)
{
UsedMap.OperationGrid[i].Check();
break;
}
}
}
/// <summary>
/// Performs an operation on the values of two dice based on the provided operation.
/// </summary>
/// <param name="o">The operation to perform. This can be LOWER, HIGHER, SUBTRACTION, ADDITION, or MULTIPLICATION.</param>
/// <returns>
/// The result of the operation. If the operation is LOWER or HIGHER, it returns the lower or higher value of the two dice respectively.
/// If the operation is SUBTRACTION, it returns the difference between the higher and lower value of the two dice.
/// If the operation is ADDITION, it returns the sum of the values of the two dice.
/// If the operation is MULTIPLICATION, it returns the product of the values of the two dice.
/// If the operation is not one of the operations, it throws an ArgumentOutOfRangeException.
/// </returns>
public int ResultOperation(Operation o)
{
int result = o switch
{
Operation.LOWER => Dice1.IsLower(Dice2) ? Dice1.Value : Dice2.Value,
Operation.HIGHER => Dice1.IsLower(Dice2) ? Dice2.Value : Dice1.Value,
Operation.SUBTRACTION => Dice1.IsLower(Dice2) ? Dice2.Value - Dice1.Value : Dice1.Value - Dice2.Value,
Operation.ADDITION => Dice2.Value + Dice1.Value,
Operation.MULTIPLICATION => Dice2.Value * Dice1.Value,
_ => throw new ArgumentOutOfRangeException()
};
MarkOperationAsChecked(o);
return result;
}
/// <summary>
/// Places the result of a dice operation into a chosen cell on the game board.
/// The result can be placed in the chosen cell if it's the first turn or if the chosen cell is valid according to the game rules.
/// </summary>
/// <param name="playerChoice">The cell chosen by the player to place the result.</param>
/// <param name="result">The result of the dice operation to be placed in the cell.</param>
private bool PlaceResult(Cell playerChoice, int result)
{
if (Turn == 1 || GameRules.NearCellIsValid(playerChoice, UsedMap.Boards.ToList()))
{
for (int i = 0; i < UsedMap.Boards.Count; i++)
{
if (UsedMap.Boards[i].X == playerChoice.X && UsedMap.Boards[i].Y == playerChoice.Y)
{
if (UsedMap.Boards[i].Value != null)
return false;
UsedMap.Boards[i].Value = result;
BoardUpdated?.Invoke(this, new BoardsUpdateEventArgs(UsedMap.Boards.ToList()));
return true;
}
}
//playerChoice.Value = result;
}
return false;
}
/// <summary>
/// Add the choosen cell to a rope path if it's possible.
/// </summary>
/// <param name="playerChoice"></param>
/// <param name="adjacentes"></param>
private void AddToRopePath(Cell playerChoice,List<Cell> adjacentes)
{
int index =0;
foreach (var cells in adjacentes.Where(cells => cells.Value - playerChoice.Value == 1 || cells.Value - playerChoice.Value == -1))
{
// Le cas si il n'existe aucun chemin de corde
if (UsedMap.RopePaths.Count == 0)
{
// Creer un nouveau chemin de corde avec la cellule choisi par le joueur et celle adjacente
UsedMap.RopePaths.Add(new List<Cell> {playerChoice, cells});
}
// A modifier dans le cas ou il est possible de fusionner deux chemins de corde
if (GameRules.IsInRopePaths(playerChoice, UsedMap.RopePaths, index)) break;
// Le cas si il existe des chemins de corde
// Est-ce que la cellule adjacentes fait parti d'un chemin de corde
if (!GameRules.IsInRopePaths(cells,UsedMap.RopePaths,index))
{
UsedMap.RopePaths.Add(new List<Cell> { playerChoice, cells });
continue;
}
if (!GameRules.AsValue(playerChoice,UsedMap.RopePaths,index))
{
UsedMap.RopePaths[index].Add(playerChoice);
}
}
}
/// <summary>
/// Initializes the game.
/// </summary>
public void InitializeGame(Map map, Player player, bool startImmediately = true)
{
UsedMap = map;
CurrentPlayer = player;
Turn = 1;
Dice1 = new Dice();
Dice2 = new Dice(1);
if (startImmediately)
{
StartGame();
}
}
/// <summary>
/// Starts the game.
/// </summary>
private void StartGame()
{
IsRunning = true;
GameStarted?.Invoke(this, new GameStartedEventArgs(CurrentPlayer));
GameLoop();
}
/// <summary>
/// Ends the game.
/// </summary>
private void EndGame(int? pts)
{
IsRunning = false;
GameEnded?.Invoke(this, new GameEndedEventArgs(CurrentPlayer, pts));
}
/// <summary>
/// The main game loop that runs while the game is active.
/// </summary>
private void GameLoop()
{
while (IsRunning)
{
if (Turn == 20)
{
foreach(var cells in UsedMap.Boards.ToList())
{
GameRules.IsZoneValidAndAddToZones(cells, UsedMap);
AddToRopePath(cells, GameRules.EveryAdjacentCells(cells, UsedMap.Boards.ToList()));
}
int? points = GameRules.FinalCalculusOfZones(UsedMap.Zones);
for (int i = 0; i < UsedMap.RopePaths.Count; i++)
{
points += GameRules.ScoreRopePaths(UsedMap.RopePaths[i]);
}
EndGame(points);
break;
}
RollAllDice();
Turn++;
}
}
/// <summary>
/// Handles the player's choice of an operation based on the dice values.s
/// </summary>
/// <param name="operation">The operation chosen by the player.</param>
public void HandlePlayerOperation(Operation operation)
{
int result = ResultOperation(operation);
OperationChosen?.Invoke(this, new OperationChosenEventArgs(operation, result));
}
/// <summary>
/// Handles the player's choice of a cell on the game board.
/// </summary>
/// <param name="cell"></param>
/// <param name="result"></param>
/// <exception cref="InvalidCellCoordinatesException"></exception>
/// <exception cref="InvalidCellException"></exception>
public bool HandlePlayerChoice(Cell cell, int result)
{
if (cell.X < 0 || cell.X >= UsedMap.Boards.Count / 6 || cell.Y < 0 || cell.Y >= 6)
{
return false;
//throw new InvalidCellCoordinatesException("Invalid cell coordinates. Please choose again.");
}
if (!GameRules.IsCellValid(cell, UsedMap.Boards.ToList()))
{
return false;
//throw new InvalidCellException("Cell is not valid. Please choose again.");
}
bool res = PlaceResult(cell, result);
if (!res)
{
return false;
//throw new InvalidPlaceResultException("Cell is not valid for place result. Please choose again.");
}
GameRules.IsZoneValidAndAddToZones(cell, UsedMap);
AddToRopePath(cell, GameRules.EveryAdjacentCells(cell, UsedMap.Boards.ToList()));
CellChosen?.Invoke(this, new CellChosenEventArgs(cell, result));
return true;
//BoardUpdated?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Event raised when a property is changed to notify the view.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// Trigger the PropertyChanged event for a specific property.
/// </summary>
/// <param name="propertyName">Name of the property that changed.</param>
public virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}