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

556 lines
19 KiB

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
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.Interfaces;
using Models.Rules;
using SQLite;
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, SQLite.Table("Games")]
public class Game : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
/* Persistence Interface */
[Ignore]
public IPersistence PersistenceManager { get; set; }
/* List for the game and persistence */
private ObservableCollection<Player> _players;
[Ignore]
public ObservableCollection<Player> Players
{
get => _players;
set
{
_players = value;
OnPropertyChanged(nameof(Players));
}
}
private ObservableCollection<Game> _games;
[Ignore]
public ObservableCollection<Game> Games
{
get => _games;
set
{
_games = value;
OnPropertyChanged(nameof(Games));
}
}
private ObservableCollection<Map> _maps;
[Ignore]
public ObservableCollection<Map> Maps
{
get => _maps;
set
{
_maps = value;
OnPropertyChanged(nameof(Maps));
}
}
private ObservableCollection<BestScore> _bestScores;
[Ignore]
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; }
private Map? _usedMap;
[DataMember]
public Map? UsedMap
{
get => _usedMap;
set
{
_usedMap = value;
OnPropertyChanged(nameof(UsedMap));
}
}
[Ignore]
public Dice Dice1 { get; private set;}
[Ignore]
public Dice Dice2 { get; private set; }
[DataMember]
public int Turn { get; private set; }
[Ignore]
public Operation PlayerOperation { get; set; }
[Ignore]
public Cell PlayerCell { get; set; }
[Ignore]
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 event EventHandler<PlayerChooseOperationEventArgs> PlayerChooseOp;
public event EventHandler<PlayerOptionEventArgs> PlayerOption;
public event EventHandler<PlayerChooseCellEventArgs> PlayerChooseCell;
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(int finalScore)
{
BestScore bs = new BestScore(UsedMap.Name, CurrentPlayer, 1, finalScore);
foreach (var score in BestScores)
{
if (!bs.Equals(score)) continue;
score.IncrGamesPlayed();
score.UpdateScore(finalScore);
return;
}
BestScores.Add(bs);
BestScores.OrderByDescending(p => p.Score);
}
public bool RemovePlayer(string playerName)
{
Player player = Players.FirstOrDefault(p => p.Pseudo == playerName);
if (player == null)
{
return false;
}
Players.Remove(player);
CheckAndRemoveBestScoresDependencies(player.Pseudo);
return true;
}
public bool ModifyPlayer(string pseudo, string newpseudo)
{
foreach (var index in Players)
{
if (index.Pseudo == pseudo)
{
CheckAndChangeBestScoresDependencies(index.Pseudo, newpseudo);
index.Pseudo = newpseudo;
return true;
}
}
return false;
}
public void CheckAndRemoveBestScoresDependencies(string playerName)
{
List<BestScore> bs = new List<BestScore>();
foreach (var bestScore in BestScores)
{
if (!bestScore.ThePlayer.Pseudo.Equals(playerName)) continue;
bs.Add(bestScore);
}
foreach (var score in bs)
{
BestScores.Remove(score);
}
}
public void CheckAndChangeBestScoresDependencies(string playerName, string newPlayerName)
{
foreach (var bestScore in BestScores)
{
if (!bestScore.ThePlayer.Pseudo.Equals(playerName)) continue;
bestScore.ThePlayer.Pseudo = newPlayerName;
}
}
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("temp","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;
IEnumerable<OperationCell> sortPaths =
from cell in UsedMap.OperationGrid
where cell.Y == operationIndex
select cell;
foreach (var item in sortPaths)
{
if (!item.IsChecked)
{
item.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()
};
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 void PlaceResult(Cell playerChoice, int result)
{
IEnumerable<Cell> ValidCell =
from cell in UsedMap.Boards
where cell.Value == null
where cell.Valid == true
select cell;
foreach (var item in ValidCell)
{
if(item.X == playerChoice.X && item.Y == playerChoice.Y)
item.Value = result;
}
}
/// <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()
{
int res = 0,turn = 1;
Cell cell;
while (IsRunning)
{
RollAllDice();
res = PlayerChooseOperation();
PlayerOption?.Invoke(this,new PlayerOptionEventArgs(UsedMap.Boards.ToList(),res,turn));
PlayerSelectionCell();
PlaceResult(PlayerCell,res);
BoardUpdated?.Invoke(this, new BoardsUpdateEventArgs(UsedMap.Boards.ToList()));
turn++;
}
}
public int PlayerChooseOperation()
{
PlayerChooseOp?.Invoke(this, new PlayerChooseOperationEventArgs(PlayerOperation));
while(!GameRules.OperationAvailable(PlayerOperation,UsedMap.OperationGrid.ToList()))
{
PlayerChooseOp?.Invoke(this, new PlayerChooseOperationEventArgs(PlayerOperation));
}
return ResultOperation(PlayerOperation);
}
public void PlayerSelectionCell()
{
PlayerChooseCell?.Invoke(this,new PlayerChooseCellEventArgs(PlayerCell));
while(!GameRules.NearCellIsValid(PlayerCell,UsedMap.Boards.ToList()))
{
PlayerChooseCell?.Invoke(this, new PlayerChooseCellEventArgs(PlayerCell));
}
MarkOperationAsChecked(PlayerOperation);
}
/// <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 = true;
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));
}
public int CalculusOfPenalty(ReadOnlyCollection<Cell> Boards)
{
int result = 0;
foreach (var cells in Boards)
if (cells.Penalty)
{
if (cells.Valid == false || cells.Value == null)
continue;
result += 3;
}
return result;
}
public void PutPenaltyForLostCells(ReadOnlyCollection<Cell> Boards)
{
foreach (var cells in Boards)
{
if (cells == null || cells.Value == null || cells.Valid == false)
continue;
if (!UsedMap.IsCellInZones(cells) && !UsedMap.IsCellInRopePath(cells))
cells.SetPenalty();
}
}
public int FinalCalculusOfPoints()
{
int? points = GameRules.FinalCalculusOfZones(UsedMap.Zones);
for (int i = 0; i < UsedMap.RopePaths.Count; i++)
{
points += GameRules.ScoreRopePaths(UsedMap.RopePaths[i]);
}
points += CalculusOfPenalty(UsedMap.Boards);
return points ?? 0;
}
}
}