Compare commits

...

39 Commits

Author SHA1 Message Date
Jérémy Mouyon 70825b1093 Mise à jour de 'README.md'
continuous-integration/drone/push Build is passing Details
3 weeks ago
Jules LASCRET d3fc957213 Mise à jour de 'README.md'
continuous-integration/drone/push Build is passing Details
10 months ago
Jérémy Mouyon d9e8a536a3 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jeremy.mouyon/sae201_qwirkle
continuous-integration/drone/push Build is passing Details
10 months ago
Jérémy Mouyon 440a329a92 back button
10 months ago
Jérémy Mouyon a09e25bca4 Mise à jour de 'README.md'
continuous-integration/drone/push Build is passing Details
11 months ago
Jérémy Mouyon c4e5cc09a3 push of zip exe
continuous-integration/drone/push Build is passing Details
11 months ago
Jérémy Mouyon 1edf1d7374 Mise à jour de 'README.md'
continuous-integration/drone/push Build is passing Details
11 months ago
Jérémy Mouyon fcfaf6ee5a Merge branch 'master' of https://codefirst.iut.uca.fr/git/jeremy.mouyon/sae201_qwirkle
continuous-integration/drone/push Build is passing Details
11 months ago
Jérémy Mouyon 4bef870bd6 trad + new message / delete message
11 months ago
Jérémy Mouyon af69cba848 good smile :)
continuous-integration/drone/push Build is passing Details
11 months ago
rportet d24b19e757 more and more documentation, again
continuous-integration/drone/push Build is passing Details
11 months ago
rportet ffbd8aba48 adding a lil more doc again
continuous-integration/drone/push Build is passing Details
11 months ago
rportet d9afae006d Merge branch 'master' of https://codefirst.iut.uca.fr/git/jeremy.mouyon/sae201_qwirkle
continuous-integration/drone/push Build is passing Details
11 months ago
rportet 2026228b71 adding a lil doc
11 months ago
Jules LASCRET 08ed17959b Split methods + new test
continuous-integration/drone/push Build is passing Details
11 months ago
Jules LASCRET 77054e7c3d Merge remote-tracking branch 'origin/master'
continuous-integration/drone/push Build is passing Details
11 months ago
Jules LASCRET 897bc547d7 Scores bloody working now, I hope otherwise I might quit
11 months ago
Jérémy Mouyon 76d047595a label meduim
continuous-integration/drone/push Build is passing Details
11 months ago
Jérémy Mouyon 019307e893 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jeremy.mouyon/sae201_qwirkle
continuous-integration/drone/push Build is passing Details
11 months ago
Jérémy Mouyon 73f1fd91ea end of game / build edit
11 months ago
Jules LASCRET b0afa799ae Test issue #3, normally back at 0 code smells & +80% coverage
continuous-integration/drone/push Build is passing Details
11 months ago
Jules LASCRET f0a3185b4b Test issue #2
continuous-integration/drone/push Build is passing Details
11 months ago
Jules LASCRET 4cc3b3361d test issue #1
continuous-integration/drone/push Build is passing Details
11 months ago
Jules LASCRET 20323c92b5 More tests and code smell fix (starting to feel bored)
continuous-integration/drone/push Build is passing Details
11 months ago
Jules LASCRET 9ea98eaa90 Fixed code smell + added test
continuous-integration/drone/push Build is passing Details
11 months ago
Jules LASCRET 3455682073 fixed all code smells normally now
continuous-integration/drone/push Build is passing Details
11 months ago
Jules LASCRET c45e859e83 fix some code smells
continuous-integration/drone/push Build is passing Details
11 months ago
Jérémy Mouyon e85e47ab26 delete sound / update framework
11 months ago
Jules LASCRET ecfcc1cb37 Fix 1 minor code smell (normally)
continuous-integration/drone/push Build is passing Details
11 months ago
Jules LASCRET 317dbeaa83 reduce complexity for CheckWrongCompletedLines
continuous-integration/drone/push Build is passing Details
11 months ago
Jules LASCRET 35134c2bbc Merge remote-tracking branch 'origin/master'
continuous-integration/drone/push Build is passing Details
11 months ago
Jules LASCRET 1073b2d802 Boum le bibi
11 months ago
rportet 0a34f4bc43 now goBack fucking works ! better dirty than not working
continuous-integration/drone/push Build is passing Details
11 months ago
rportet 6ea1bdc7f0 j'ai push le mauvais truc mdrr
continuous-integration/drone/push Build is passing Details
11 months ago
rportet ad0e5be725 test for the app.xaml
continuous-integration/drone/push Build is passing Details
11 months ago
Jérémy Mouyon 1009c742f8 push
continuous-integration/drone/push Build is passing Details
11 months ago
Jérémy Mouyon d16d488477 revert branch by remy without sound
continuous-integration/drone/push Build is passing Details
11 months ago
Jérémy Mouyon 448a44386b Merge branch 'master' of https://codefirst.iut.uca.fr/git/jeremy.mouyon/sae201_qwirkle
continuous-integration/drone/push Build is passing Details
11 months ago
Jérémy Mouyon 332ac4979a sound + up end game
11 months ago

File diff suppressed because one or more lines are too long

@ -53,16 +53,16 @@ namespace QwirkleClassLibrary.Boards
{ {
for (int b = 0; b < Columns; b++) for (int b = 0; b < Columns; b++)
{ {
Cell localcell = new(a, b); Cell localcell = new(b, a);
cells.Add(localcell); cells.Add(localcell);
} }
} }
} }
/// <summary> /// <summary>
/// This method is used to check if a cell in the board whether it already contains a tile or not. /// This method is used to check if a cell in the board whether already contains a tile or not.
/// </summary> /// </summary>
/// <returns>Returns a boolean : true if the cell doesn't contain any tile, false if it already contains a tile.</returns> /// <returns>Returns a boolean : true if the cell doesn't contain any tile, false if already contains a tile.</returns>
public bool HasOccupiedCase() public bool HasOccupiedCase()
{ {
foreach (var cell in cells) foreach (var cell in cells)

@ -4,89 +4,94 @@ using System.Runtime.CompilerServices;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using QwirkleClassLibrary.Tiles; using QwirkleClassLibrary.Tiles;
namespace QwirkleClassLibrary.Boards; namespace QwirkleClassLibrary.Boards
[DataContract]
public class Cell : INotifyPropertyChanged
{ {
public event PropertyChangedEventHandler? PropertyChanged;
[DataMember]
private readonly int x;
[DataMember]
private readonly int y;
[DataMember]
public Tile? Tile { get; private set;}
/// <summary> /// <summary>
/// This is the constructor for a Cell. /// Our board is made with a list of this class. It can stock infos such as its position on the board and the tile it contains.
/// </summary> /// </summary>
/// <param name="x">The x attribute of the cell.</param> [DataContract]
/// <param name="y">The y attribute of the cell.</param> public class Cell : INotifyPropertyChanged
/// <exception cref="ArgumentException">Throw an exception if the x or y attribute is negative.</exception>
public Cell(int x, int y)
{ {
if (x < 0 || y < 0) public event PropertyChangedEventHandler? PropertyChanged;
{
throw new ArgumentException(x.ToString() + y.ToString());
}
this.x = x; [DataMember]
this.y = y; private readonly int x;
}
/// <summary> [DataMember]
/// A getter for the position of the cell on the x-axis. private readonly int y;
/// </summary>
/// <returns>The position of the cell on the x-axis.</returns>
public int GetX
{
get { return x; }
}
/// <summary> [DataMember]
/// A getter for the position of the cell on the y-axis. public Tile? Tile { get; private set; }
/// </summary>
/// <returns>The position of the cell on the y-axis.</returns>
public int GetY
{
get { return y; }
}
/// <summary> /// <summary>
/// Check if the Cell whether is empty or contains a tile. /// This is the constructor for a Cell.
/// </summary> /// </summary>
/// <returns>True if the cell is empty, false if the cell contains a tile.</returns> /// <param name="x">The x attribute of the cell.</param>
public bool IsFree /// <param name="y">The y attribute of the cell.</param>
{ /// <exception cref="ArgumentException">Throw an exception if the x or y attribute is negative.</exception>
get { return Tile == null; } public Cell(int x, int y)
} {
if (x < 0 || y < 0)
{
throw new ArgumentException(x.ToString() + y.ToString());
}
this.x = x;
this.y = y;
}
/// <summary> /// <summary>
/// A setter for the tile in the cell. /// A getter for the position of the cell on the x-axis.
/// </summary> /// </summary>
/// <param name="addedTile">The tile the player want to add in the cell.</param> /// <returns>The position of the cell on the x-axis.</returns>
/// <returns>True if added succefully (if the cell didn't already contain a tile), false if there already was a tile in this cell.</returns> public int GetX
public bool SetTile(Tile addedTile) {
{ get { return x; }
if (Tile == null) }
/// <summary>
/// A getter for the position of the cell on the y-axis.
/// </summary>
/// <returns>The position of the cell on the y-axis.</returns>
public int GetY
{ {
get { return y; }
}
Tile = addedTile; /// <summary>
OnPropertyChanged(nameof(Tile)); /// Checks if the Cell whether is empty or contains a tile.
return true; /// </summary>
/// <returns>True if the cell is empty, false if the cell contains a tile.</returns>
public bool IsFree
{
get { return Tile! == null!; }
} }
else
/// <summary>
/// A setter for the tile in the cell.
/// </summary>
/// <param name="addedTile">The tile the player want to add in the cell.</param>
/// <returns>True if added succefully (if the cell didn't already contain a tile), false if there already was a tile in this cell.</returns>
public bool SetTile(Tile addedTile)
{ {
return false; if (Tile! == null!)
{
Tile = addedTile;
OnPropertyChanged(nameof(Tile));
return true;
}
else
{
return false;
}
} }
}
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{ {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
} }
} }

@ -7,43 +7,37 @@ using QwirkleClassLibrary.Events;
using QwirkleClassLibrary.Players; using QwirkleClassLibrary.Players;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using static System.Formats.Asn1.AsnWriter;
namespace QwirkleClassLibrary.Games namespace QwirkleClassLibrary.Games
{ {
/// <summary>
/// This is our main class for the Qwirkle application, taking care of the good efficiency of the game.
/// </summary>
[DataContract] [DataContract]
public class Game : IPlayer, IRules, INotifyPropertyChanged public class Game : IPlayer, IRules, INotifyPropertyChanged
{ {
[DataMember] [DataMember] private TileBag? bag = null;
private TileBag? bag = null;
[DataMember] [DataMember] public bool GameRunning { get; set; }
public bool GameRunning { get; set; }
[DataMember] [DataMember] private Board board = new(17, 14);
private Board board = new(17, 14);
public bool PlayerSwapping { get; set; } public bool PlayerSwapping { get; set; }
public Board Board => board; public Board Board => board;
public ReadOnlyCollection<Player> PlayerList => players.AsReadOnly(); public ReadOnlyCollection<Player> PlayerList => players.AsReadOnly();
[DataMember] [DataMember] private readonly List<Player> players = [];
private readonly List<Player> players = [];
[DataMember] [DataMember] private readonly Dictionary<string, int> scoreBoard = new Dictionary<string, int>();
private readonly Dictionary<string, int> scoreBoard = new Dictionary<string, int>();
public ReadOnlyDictionary<string, int> ScoreBoard => scoreBoard.AsReadOnly(); public ReadOnlyDictionary<string, int> ScoreBoard => scoreBoard.AsReadOnly();
[DataMember] [DataMember] private readonly ObservableCollection<KeyValuePair<string, int>> observableScoreBoard = [];
private readonly ObservableCollection<KeyValuePair<string, int>> observableScoreBoard = [];
public ReadOnlyObservableCollection<KeyValuePair<string, int>> ObservableScoreBoard => public ReadOnlyObservableCollection<KeyValuePair<string, int>> ObservableScoreBoard =>
new(observableScoreBoard); new(observableScoreBoard);
[DataMember] [DataMember] private readonly List<Cell> cellUsed = [];
private readonly List<Cell> cellUsed = [];
public ReadOnlyCollection<Cell> CellsUsed => cellUsed.AsReadOnly(); public ReadOnlyCollection<Cell> CellsUsed => cellUsed.AsReadOnly();
@ -104,9 +98,11 @@ namespace QwirkleClassLibrary.Games
if (playersTag.Count <= 1 || playersTag.Count > 4) if (playersTag.Count <= 1 || playersTag.Count > 4)
{ {
playersTag.Clear(); playersTag.Clear();
OnPlayerNotified(new AddPlayerNotifiedEventArgs("ERROR : It takes a minimum of 2 players and a maximum of 4 players to start a game.")); OnPlayerNotified(new AddPlayerNotifiedEventArgs(
"ERROR : It takes a minimum of 2 players and a maximum of 4 players to start a game."));
return false; return false;
} }
for (int i = playersTag.Count - 1; i >= 0; i--) for (int i = playersTag.Count - 1; i >= 0; i--)
{ {
if (!CheckPlayerTag(playersTag, i)) if (!CheckPlayerTag(playersTag, i))
@ -126,12 +122,18 @@ namespace QwirkleClassLibrary.Games
OnPlayerNotified(new AddPlayerNotifiedEventArgs("Players were correctly added.")); OnPlayerNotified(new AddPlayerNotifiedEventArgs("Players were correctly added."));
return true; return true;
} }
/// <summary>
/// This function is used to check if the player name that the user has entered meets the criteria set by the application.
/// </summary>
/// <param name="playersTag">A list that contains all the names entered when the game was started.</param>
/// <param name="pos">The position of the name we want to check in this list.</param>
/// <returns>boolean true if everything is okay, false if there was a problem in the player name.</returns>
public bool CheckPlayerTag(List<string> playersTag, int pos) public bool CheckPlayerTag(List<string> playersTag, int pos)
{ {
if (string.IsNullOrWhiteSpace(playersTag[pos])) if (string.IsNullOrWhiteSpace(playersTag[pos]))
{ {
OnPlayerNotified(new AddPlayerNotifiedEventArgs("ERROR with " + (pos + 1) + " entry : The name is null or white space.")); OnPlayerNotified(new AddPlayerNotifiedEventArgs("ERROR with " + (pos + 1) +
" entry : The name is null or white space."));
return false; return false;
} }
@ -148,7 +150,6 @@ namespace QwirkleClassLibrary.Games
new AddPlayerNotifiedEventArgs("ERROR with " + (pos + 1) + " entry : Name alreay taken")); new AddPlayerNotifiedEventArgs("ERROR with " + (pos + 1) + " entry : Name alreay taken"));
return false; return false;
} }
} }
return true; return true;
@ -170,13 +171,19 @@ namespace QwirkleClassLibrary.Games
/// Returns the Board of the game /// Returns the Board of the game
/// </summary> /// </summary>
/// <returns>Board</returns> /// <returns>Board</returns>
public Board? GetBoard() { return board; } public Board GetBoard()
{
return board;
}
/// <summary> /// <summary>
/// Returns the tile bag of the game /// Returns the tile bag of the game
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public TileBag? GetTileBag() { return bag; } public TileBag? GetTileBag()
{
return bag;
}
/// <summary> /// <summary>
/// Creates a Board with a number of columns and rows /// Creates a Board with a number of columns and rows
@ -238,6 +245,7 @@ namespace QwirkleClassLibrary.Games
{ {
throw new ArgumentException("No player currently playing !"); throw new ArgumentException("No player currently playing !");
} }
return players[GetPlayingPlayerPosition()]; return players[GetPlayingPlayerPosition()];
} }
@ -254,6 +262,7 @@ namespace QwirkleClassLibrary.Games
return i; return i;
} }
} }
return -1; return -1;
} }
@ -316,7 +325,6 @@ namespace QwirkleClassLibrary.Games
startingPlayer!.IsPlaying = true; startingPlayer!.IsPlaying = true;
OnNextPlayer(new NextPlayerNotifiedEventArgs(players[0])); OnNextPlayer(new NextPlayerNotifiedEventArgs(players[0]));
return startingPlayer.NameTag; return startingPlayer.NameTag;
} }
/// <summary> /// <summary>
@ -354,13 +362,15 @@ namespace QwirkleClassLibrary.Games
OnPlaceTile(new PlaceTileNotifiedEventArgs(tile, "you are swapping, you can't place tile !")); OnPlaceTile(new PlaceTileNotifiedEventArgs(tile, "you are swapping, you can't place tile !"));
return false; return false;
} }
if (!TileInbag(player, tile)) if (!TileInbag(player, tile))
{ {
OnPlaceTile(new PlaceTileNotifiedEventArgs(tile, "you can't play")); OnPlaceTile(new PlaceTileNotifiedEventArgs(tile, "you can't play"));
return false; return false;
} }
if (!IsMoveCorrect(tile, x, y, board!)) return false;
if (board!.AddTileInCell(x, y, tile)) if (!IsMoveCorrect(tile, x, y, board)) return false;
if (board.AddTileInCell(x, y, tile))
{ {
AddCellUsed(board.GetCell(x, y)); AddCellUsed(board.GetCell(x, y));
return player.RemoveTileToPlayer(tile); return player.RemoveTileToPlayer(tile);
@ -446,12 +456,12 @@ namespace QwirkleClassLibrary.Games
{ {
players[GetPlayingPlayerPosition()].AddTileToPlayer(t); players[GetPlayingPlayerPosition()].AddTileToPlayer(t);
} }
} }
/// <summary> /// <summary>
/// Extension of IsMoveCorrect to check beyond the surrounding cells of the cell where the tile is placed /// Extension of IsMoveCorrect to check beyond the surrounding cells of the cell where the tile is placed
/// </summary> /// </summary>
/// <param name="previousTilesFound"></param>
/// <param name="tile"></param> /// <param name="tile"></param>
/// <param name="x"></param> /// <param name="x"></param>
/// <param name="y"></param> /// <param name="y"></param>
@ -459,26 +469,39 @@ namespace QwirkleClassLibrary.Games
/// <param name="dy">used to get the direction on the y axis</param> /// <param name="dy">used to get the direction on the y axis</param>
/// <param name="b"></param> /// <param name="b"></param>
/// <returns>bool</returns> /// <returns>bool</returns>
public bool CheckExtendedSurroundingCells(Tile tile, int x, int y, int dx, int dy, Board b) public bool CheckExtendedSurroundingCells(ref bool previousTilesFound, Tile tile, int x, int y, int dx, int dy,
Board b)
{ {
for (int i = 1; i < 7; i++) for (int i = 1; i < 7; i++)
{ {
var extendedCell = b.GetCell(x + i * dx, y + i * dy); var extendedCell = b.GetCell(x + i * dx, y + i * dy);
if (extendedCell?.Tile == null) if (cellUsed.Count == 0)
{
previousTilesFound = true;
}
if (cellUsed.Contains(extendedCell!))
{
previousTilesFound = true;
}
if (extendedCell?.Tile! == null!)
{ {
break; break;
} }
if (extendedCell.Tile.GetColor != tile.GetColor && extendedCell.Tile.GetShape != tile.GetShape) if (extendedCell.Tile.GetColor != tile.GetColor && extendedCell.Tile.GetShape != tile.GetShape)
{ {
OnPlaceTile(new PlaceTileNotifiedEventArgs(tile, " : Color / Shape does not match with the surrounding tiles !")); OnPlaceTile(new PlaceTileNotifiedEventArgs(tile,
" : Color / Shape does not match with the surrounding tiles !"));
return false; return false;
} }
if (extendedCell.Tile.GetColor == tile.GetColor && extendedCell.Tile.GetShape == tile.GetShape) if (extendedCell.Tile.GetColor == tile.GetColor && extendedCell.Tile.GetShape == tile.GetShape)
{ {
OnPlaceTile(new PlaceTileNotifiedEventArgs(tile, " : Tile already placed on the same line / column !")); OnPlaceTile(new PlaceTileNotifiedEventArgs(tile,
" : Tile already placed on the same line / column !"));
return false; return false;
} }
@ -527,6 +550,7 @@ namespace QwirkleClassLibrary.Games
{ {
return x == x1; return x == x1;
} }
if (y1 == y2) if (y1 == y2)
{ {
return y == y1; return y == y1;
@ -536,13 +560,62 @@ namespace QwirkleClassLibrary.Games
} }
/// <summary> /// <summary>
/// Main method to check if the move the player is trying to make is correct /// Check that there isn't any same tile on a said line when a tile is forming a line
/// </summary>
/// <param name="t1"></param>
/// <param name="nbTiles"></param>
/// <param name="checkdoubles"></param>
/// <returns></returns>
public static bool CheckTileInCompletedLines(Tile? t1, ref int nbTiles, ref List<Tile> checkdoubles)
{
if (t1! != null!)
{
nbTiles++;
if (checkdoubles.Any(t => t.CompareTo(t1) == 0))
{
return false;
}
checkdoubles.Add(t1);
}
return true;
}
/// <summary>
/// Check if the line is completed with the tile placed
/// </summary> /// </summary>
/// <param name="t"></param> /// <param name="tile"></param>
/// <param name="x"></param> /// <param name="x"></param>
/// <param name="y"></param> /// <param name="y"></param>
/// <param name="dx"></param>
/// <param name="dy"></param>
/// <param name="b"></param> /// <param name="b"></param>
/// <returns>bool</returns> /// <param name="checkdoubles"></param>
/// <returns></returns>
public static bool CheckWrongCompletedLines(int x, int y, int dx, int dy, Board b, ref List<Tile> checkdoubles)
{
int nbTiles = 1;
for (int i = 1; i < 7; i++)
{
var extendedCell = b.GetCell(x + i * dx, y + i * dy);
var extendedCell2 = b.GetCell(x - i * dx, y - i * dy);
if (extendedCell?.Tile! == null! && extendedCell2?.Tile! == null!)
{
break;
}
if(!CheckTileInCompletedLines(extendedCell?.Tile, ref nbTiles, ref checkdoubles)) return false;
if(!CheckTileInCompletedLines(extendedCell2?.Tile, ref nbTiles, ref checkdoubles)) return false;
}
return nbTiles <= 6;
}
public bool IsMoveCorrect(Tile t, int x, int y, Board b) public bool IsMoveCorrect(Tile t, int x, int y, Board b)
{ {
if (!b.HasOccupiedCase()) if (!b.HasOccupiedCase())
@ -550,9 +623,10 @@ namespace QwirkleClassLibrary.Games
return true; return true;
} }
if (b.GetCell(x, y)!.Tile != null) if (b.GetCell(x, y)!.Tile! != null!)
{ {
OnPlaceTile(new PlaceTileNotifiedEventArgs(t, " : Cell already used !")); OnPlaceTile(new PlaceTileNotifiedEventArgs(t, " : Cell already used !"));
return false;
} }
var surroundingCells = new List<Cell?> var surroundingCells = new List<Cell?>
@ -563,49 +637,67 @@ namespace QwirkleClassLibrary.Games
b.GetCell(x, y - 1) b.GetCell(x, y - 1)
}; };
if (surroundingCells.All(cell => cell?.Tile! == null!))
{
OnPlaceTile(new PlaceTileNotifiedEventArgs(t,
" : You can't place a tile that isn't adjacent to another one !"));
return false;
}
return IsTilePlacementCorrect(t, x, y, b, surroundingCells);
}
public bool IsTilePlacementCorrect(Tile t, int x, int y, Board b, List<Cell?> surroundingCells)
{
bool previousTilesFound = false;
var checkDoubles = new List<Tile>();
foreach (var cell in surroundingCells) foreach (var cell in surroundingCells)
{ {
if (cell?.Tile == null) if (cell?.Tile! == null!)
{ {
continue; continue;
} }
if (cell.Tile.GetColor != t.GetColor && cell.Tile.GetShape != t.GetShape) if (cell.Tile.GetColor != t.GetColor && cell.Tile.GetShape != t.GetShape)
{ {
OnPlaceTile(new PlaceTileNotifiedEventArgs(t, " : Colors / Shapes do not match with the surrounding tiles !")); OnPlaceTile(new PlaceTileNotifiedEventArgs(t,
" : Colors / Shapes do not match with the surrounding tiles !"));
return false; return false;
} }
if (cell.Tile.GetColor == t.GetColor && cell.Tile.GetShape == t.GetShape) if (cell.Tile.GetColor == t.GetColor && cell.Tile.GetShape == t.GetShape)
{ {
OnPlaceTile(new PlaceTileNotifiedEventArgs(t, " is already placed on the same line / column !")); OnPlaceTile(new PlaceTileNotifiedEventArgs(t, " is already placed on the same line / column !"));
return false; return false;
} }
var dx = cell.GetX - x; var dx = cell.GetX - x;
var dy = cell.GetY - y; var dy = cell.GetY - y;
if (!CheckExtendedSurroundingCells(t, x, y, dx, dy, b)) if (!CheckExtendedSurroundingCells(ref previousTilesFound, t, x, y, dx, dy, b))
{ {
return false; return false;
} }
}
if (CheckWrongCompletedLines(x, y, dx, dy, b, ref checkDoubles)) continue;
if (!CheckTilesInLine(cellUsed, b, x, y)) OnPlaceTile(new PlaceTileNotifiedEventArgs(t,
{ " : You can't complete this line ! (More than 6 tiles / same tiles on the line)"));
OnPlaceTile(new PlaceTileNotifiedEventArgs(t, "isn't on the same line as the ones previously placed !"));
return false; return false;
} }
if (surroundingCells.All(cell => cell?.Tile == null)) if (!CheckTilesInLine(cellUsed, b, x, y))
{ {
OnPlaceTile(new PlaceTileNotifiedEventArgs(t, " : You can't place a tile that isn't adjacent to another one !")); OnPlaceTile(new PlaceTileNotifiedEventArgs(t,
"isn't on the same line as the ones previously placed !"));
return false; return false;
} }
return true; if (previousTilesFound) return true;
OnPlaceTile(new PlaceTileNotifiedEventArgs(t,
" : You must place your tile next / on the same line as the ones previously placed !"));
return false;
} }
@ -625,6 +717,9 @@ namespace QwirkleClassLibrary.Games
int score = cellsPlayed.Count; int score = cellsPlayed.Count;
int nbCellsInLine = cellsPlayed.Count; int nbCellsInLine = cellsPlayed.Count;
int nbCellsInPerpLine = 1;
var checkedCells = new List<Cell>();
if (cellsPlayed.Count == 6) if (cellsPlayed.Count == 6)
{ {
@ -654,16 +749,16 @@ namespace QwirkleClassLibrary.Games
cellsX = cellsY = -1; cellsX = cellsY = -1;
} }
score += cellsPlayed.Sum(cell => CalculateAdjacentScore(cell, b, cellsPlayed, cellsX, cellsY, ref nbCellsInLine)); score += cellsPlayed.Sum(cell =>
CalculateAdjacentScore(cell, b, cellsPlayed, new Tuple<int, int>(cellsX, cellsY), ref nbCellsInLine, ref nbCellsInPerpLine, ref checkedCells));
if (nbCellsInLine == 6) if (nbCellsInLine == 6 || nbCellsInPerpLine == 6)
{ {
score += 6; score += 6;
} }
if (!scoreBoard.TryAdd(player.NameTag, score)) if (!scoreBoard.TryAdd(player.NameTag, score))
{ {
scoreBoard.TryGetValue(player.NameTag, out int scoreold); scoreBoard.TryGetValue(player.NameTag, out int scoreold);
SetScoreBoard(player.NameTag, score + scoreold); SetScoreBoard(player.NameTag, score + scoreold);
} }
@ -677,11 +772,12 @@ namespace QwirkleClassLibrary.Games
/// <param name="cell"></param> /// <param name="cell"></param>
/// <param name="b"></param> /// <param name="b"></param>
/// <param name="cellsPlayed"></param> /// <param name="cellsPlayed"></param>
/// <param name="cellsX"></param> /// <param name="orientation"></param>
/// <param name="cellsY"></param> /// <param name="nbCellsInPerpLine"></param>
/// <param name="checkedCells"></param>
/// <param name="nbCellsInLine"></param> /// <param name="nbCellsInLine"></param>
/// <returns>int</returns> /// <returns>int</returns>
public int CalculateAdjacentScore(Cell cell, Board b, ReadOnlyCollection<Cell> cellsPlayed, int cellsX, int cellsY, ref int nbCellsInLine) public int CalculateAdjacentScore(Cell cell, Board b, ReadOnlyCollection<Cell> cellsPlayed, Tuple<int, int> orientation, ref int nbCellsInLine, ref int nbCellsInPerpLine, ref List<Cell> checkedCells)
{ {
int score = 0; int score = 0;
@ -693,11 +789,10 @@ namespace QwirkleClassLibrary.Games
b.GetCell(cell.GetX, cell.GetY - 1) b.GetCell(cell.GetX, cell.GetY - 1)
}; };
var checkedSurroundingCells = new List<Cell>();
foreach (var adjacentCell in surroundingCells) foreach (var adjacentCell in surroundingCells)
{ {
if (adjacentCell?.Tile == null || cellsPlayed.Contains(adjacentCell) || checkedSurroundingCells.Contains(adjacentCell)) if (adjacentCell?.Tile! == null! || cellsPlayed.Contains(adjacentCell) ||
checkedCells.Contains(adjacentCell))
{ {
continue; continue;
} }
@ -705,9 +800,10 @@ namespace QwirkleClassLibrary.Games
int dx = adjacentCell.GetX - cell.GetX; int dx = adjacentCell.GetX - cell.GetX;
int dy = adjacentCell.GetY - cell.GetY; int dy = adjacentCell.GetY - cell.GetY;
score += CalculateLineScore(cellsPlayed, cell, new Tuple<int, int>(dx, dy), b, new Tuple<int, int>(cellsX, cellsY), ref nbCellsInLine); score += CalculateLineScore(cellsPlayed, cell, new Tuple<int, int>(dx, dy),
new Tuple<int, int>(orientation.Item1, orientation.Item2), ref nbCellsInLine, ref nbCellsInPerpLine, ref checkedCells);
checkedSurroundingCells.Add(adjacentCell); checkedCells.Add(adjacentCell);
} }
return score; return score;
@ -719,19 +815,20 @@ namespace QwirkleClassLibrary.Games
/// <param name="cellsPlayed"></param> /// <param name="cellsPlayed"></param>
/// <param name="cell"></param> /// <param name="cell"></param>
/// <param name="direction"></param> /// <param name="direction"></param>
/// <param name="b"></param>
/// <param name="orientation"></param> /// <param name="orientation"></param>
/// <param name="nbCellsInLine"></param> /// <param name="nbCellsInLine"></param>
/// <param name="nbCellsInPerpLine"></param>
/// <param name="checkedCells"></param>
/// <returns>int</returns> /// <returns>int</returns>
public int CalculateLineScore(ReadOnlyCollection<Cell> cellsPlayed, Cell cell, Tuple<int, int> direction, Board b, Tuple<int, int> orientation, ref int nbCellsInLine) public int CalculateLineScore(ReadOnlyCollection<Cell> cellsPlayed, Cell cell, Tuple<int, int> direction, Tuple<int, int> orientation, ref int nbCellsInLine, ref int nbCellsInPerpLine, ref List<Cell> checkedCells)
{ {
int score = 0; int score = 0;
for (int i = 1; i < 6; i++) for (int i = 1; i < 6; i++)
{ {
var extendedCell = b.GetCell(cell.GetX + i * direction.Item1, cell.GetY + i * direction.Item2); var extendedCell = board.GetCell(cell.GetX + i * direction.Item1, cell.GetY + i * direction.Item2);
if (extendedCell?.Tile == null || cellsPlayed.Contains(extendedCell)) if (extendedCell?.Tile! == null! || cellsPlayed.Contains(extendedCell) || checkedCells.Contains(extendedCell))
{ {
break; break;
} }
@ -741,10 +838,16 @@ namespace QwirkleClassLibrary.Games
nbCellsInLine++; nbCellsInLine++;
} }
if (direction.Item1 != 0 && orientation.Item1 != -1 || direction.Item2 != 0 && orientation.Item2 != -1)
{
nbCellsInPerpLine++;
}
checkedCells.Add(extendedCell);
score++; score++;
} }
if (direction.Item1 == 0 && orientation.Item1 == -1 && orientation.Item2 != -1 || direction.Item2 == 0 && orientation.Item2 == -1 && orientation.Item1 != -1) if (ShouldIncreaseScore(direction, orientation))
{ {
score += 1; score += 1;
} }
@ -752,6 +855,11 @@ namespace QwirkleClassLibrary.Games
return score; return score;
} }
public static bool ShouldIncreaseScore(Tuple<int, int> direction, Tuple<int, int> orientation)
{
return direction.Item1 == 0 && orientation.Item1 == -1 && orientation.Item2 != -1 ||
direction.Item2 == 0 && orientation.Item2 == -1 && orientation.Item1 != -1;
}
/// <summary> /// <summary>
/// Returns the list of the positions of the players who still have tiles in their bag /// Returns the list of the positions of the players who still have tiles in their bag
@ -770,7 +878,6 @@ namespace QwirkleClassLibrary.Games
playerTilesBagPos.Add(i); playerTilesBagPos.Add(i);
} }
} }
} }
return playerTilesBagPos; return playerTilesBagPos;
@ -787,7 +894,7 @@ namespace QwirkleClassLibrary.Games
{ {
foreach (var t in players[t1].Tiles) foreach (var t in players[t1].Tiles)
{ {
for (int b = 0; b < board!.ReadCells.Count; b++) for (int b = 0; b < board.ReadCells.Count; b++)
{ {
int x = board.ReadCells[b].GetX; int x = board.ReadCells[b].GetX;
int y = board.ReadCells[b].GetY; int y = board.ReadCells[b].GetY;
@ -813,7 +920,8 @@ namespace QwirkleClassLibrary.Games
{ {
List<int> playerTilesBagPos = CheckTilesBag(); List<int> playerTilesBagPos = CheckTilesBag();
if (playerTilesBagPos.Count != 0 && !CheckPlacementPossibilities(playerTilesBagPos) || bag!.TilesBag!.Count == 0 && players[GetPlayingPlayerPosition()].Tiles.Count == 0) if (playerTilesBagPos.Count != 0 && !CheckPlacementPossibilities(playerTilesBagPos) ||
bag!.TilesBag!.Count == 0 && players[GetPlayingPlayerPosition()].Tiles.Count == 0)
{ {
OnEndOfGame(new EndOfGameNotifiedEventArgs(player)); OnEndOfGame(new EndOfGameNotifiedEventArgs(player));
GameRunning = false; GameRunning = false;
@ -838,7 +946,6 @@ namespace QwirkleClassLibrary.Games
public void SetScoreBoard(string name, int score) public void SetScoreBoard(string name, int score)
{ {
if (!scoreBoard.TryAdd(name, score)) if (!scoreBoard.TryAdd(name, score))
{ {
scoreBoard[name] = score; scoreBoard[name] = score;
@ -849,6 +956,7 @@ namespace QwirkleClassLibrary.Games
{ {
observableScoreBoard.Add(item); observableScoreBoard.Add(item);
} }
OnPropertyChanged(nameof(ObservableScoreBoard)); OnPropertyChanged(nameof(ObservableScoreBoard));
} }
} }

@ -3,25 +3,32 @@ using QwirkleClassLibrary.Boards;
using QwirkleClassLibrary.Players; using QwirkleClassLibrary.Players;
using QwirkleClassLibrary.Tiles; using QwirkleClassLibrary.Tiles;
namespace QwirkleClassLibrary.Games; namespace QwirkleClassLibrary.Games
public interface IPlayer
{ {
public Player CreatePlayer(string playerTag);
public string SetNextPlayer(); /// <summary>
/// This interface is used for all methods related to the player, such as the moves he can make.
/// </summary>
public interface IPlayer
{
public Player CreatePlayer(string playerTag);
public string SetNextPlayer();
public string SetFirstPlayer(ReadOnlyCollection<Player> playingPlayers); public string SetFirstPlayer(ReadOnlyCollection<Player> playingPlayers);
public bool PlaceTile(Player player, Tile tile, int x, int y); public bool PlaceTile(Player player, Tile tile, int x, int y);
public bool DrawTiles(Player player); public bool DrawTiles(Player player);
public bool SwapTiles(Player player, List<Tile> tilesToSwap); public bool SwapTiles(Player player, List<Tile> tilesToSwap);
public int GetPlayerScore(Player player, ReadOnlyCollection<Cell> cellsPlayed, Board b); public int GetPlayerScore(Player player, ReadOnlyCollection<Cell> cellsPlayed, Board b);
int CalculateAdjacentScore(Cell cell, Board b, ReadOnlyCollection<Cell> cellsPlayed, int cellsX, int cellsY, ref int nbCellsInLine); int CalculateAdjacentScore(Cell cell, Board b, ReadOnlyCollection<Cell> cellsPlayed, Tuple<int, int> orientation,
ref int nbCellsInLine, ref int nbCellsInPerpLine, ref List<Cell> checkedCells);
int CalculateLineScore(ReadOnlyCollection<Cell> cellsPlayed, Cell cell, Tuple<int, int> direction, Board b, Tuple<int, int> orientation, ref int nbCellsInLine); int CalculateLineScore(ReadOnlyCollection<Cell> cellsPlayed, Cell cell, Tuple<int, int> direction,
Tuple<int, int> orientation, ref int nbCellsInLine, ref int nbCellsInPerpLine, ref List<Cell> checkedCells);
}
} }

@ -9,6 +9,9 @@ using QwirkleClassLibrary.Tiles;
namespace QwirkleClassLibrary.Games namespace QwirkleClassLibrary.Games
{ {
/// <summary>
/// This interface is used to define the functions used in the game. It is mainly about all the rules of the games, checking if the player moves are correct.
/// </summary>
public interface IRules public interface IRules
{ {
Board CreateBoard(); Board CreateBoard();
@ -17,7 +20,7 @@ namespace QwirkleClassLibrary.Games
bool IsMoveCorrect(Tile t, int x, int y, Board b); bool IsMoveCorrect(Tile t, int x, int y, Board b);
bool CheckExtendedSurroundingCells(Tile tile, int x, int y, int dx, int dy, Board b); bool CheckExtendedSurroundingCells(ref bool previousTilesFound, Tile tile, int x, int y, int dx, int dy, Board b);
bool CheckTilesInLine(List<Cell> cells, Board b, int x, int y); bool CheckTilesInLine(List<Cell> cells, Board b, int x, int y);

@ -1,36 +1,47 @@
using System.Runtime.Serialization; using System.Runtime.Serialization;
using QwirkleClassLibrary.Games; using QwirkleClassLibrary.Games;
namespace QwirkleClassLibrary.Persistences; namespace QwirkleClassLibrary.Persistences
public class GamePersistenceXml : IGamePersistence
{ {
public void SaveGame(Game game) /// <summary>
/// This class takes care of managing persistence with regard to the information of the current game, allowing the last game played to be resumed even when returning to the menu or exiting the application.
/// </summary>
public class GamePersistenceXml : IGamePersistence
{ {
var serializer = new DataContractSerializer(typeof(Game), /// <summary>
new DataContractSerializerSettings() { PreserveObjectReferences = true }); /// The main purpose of this method is to save the data from the game when the user quits the app, so players can continue it later.
/// </summary>
using (Stream writer = File.Create("Game.xml")) /// <param name="game"></param>
public void SaveGame(Game game)
{ {
serializer.WriteObject(writer, game); var serializer = new DataContractSerializer(typeof(Game),
} new DataContractSerializerSettings() { PreserveObjectReferences = true });
}
public Game LoadGame()
{
var serializer = new DataContractSerializer(typeof(Game));
try using (Stream writer = File.Create("Game.xml"))
{
using (Stream reader = File.OpenRead("Game.xml"))
{ {
var newGame = serializer.ReadObject(reader) as Game; serializer.WriteObject(writer, game);
return newGame!;
} }
} }
catch /// <summary>
/// This method is used to retrieve the information needed to resume the last game launched on the application.
/// </summary>
/// <returns>A Game.</returns>
public Game LoadGame()
{ {
return new Game(); var serializer = new DataContractSerializer(typeof(Game));
try
{
using (Stream reader = File.OpenRead("Game.xml"))
{
var newGame = serializer.ReadObject(reader) as Game;
return newGame!;
}
}
catch
{
return new Game();
}
} }
} }
} }

@ -1,27 +1,39 @@
using System.Runtime.Serialization.Json; using System.Runtime.Serialization.Json;
using QwirkleClassLibrary.Players; using QwirkleClassLibrary.Players;
namespace QwirkleClassLibrary.Persistences; namespace QwirkleClassLibrary.Persistences
public class LeaderboardPersistenceJson : ILeaderboardPersistence
{ {
public void SaveLeaderboard(Leaderboard leaderboard) /// <summary>
/// This is the persistence class for the leaderboard : it is in charge of managing all the parameters necessary for the backup and recovery of data concerning the leaderboard.
/// </summary>
public class LeaderboardPersistenceJson : ILeaderboardPersistence
{ {
var serializer = new DataContractJsonSerializer(typeof(Leaderboard)); /// <summary>
/// As the name suggest, this class is used to save the data from the leaderboard.
using (Stream writer = File.Create("Leaderboard.json")) /// </summary>
/// <param name="leaderboard">The current leaderboard we want to save data from.</param>
public void SaveLeaderboard(Leaderboard leaderboard)
{ {
serializer.WriteObject(writer, leaderboard); var serializer = new DataContractJsonSerializer(typeof(Leaderboard));
}
}
public Leaderboard LoadLeaderboard() using (Stream writer = File.Create("Leaderboard.json"))
{ {
var serializer = new DataContractJsonSerializer(typeof(Leaderboard)); serializer.WriteObject(writer, leaderboard);
}
using (Stream reader = File.OpenRead("Leaderboard.json")) }
/// <summary>
/// This method is used to load the leaderboard into the app when the application starts.
/// </summary>
/// <returns>Leaderboard</returns>
/// <exception cref="InvalidOperationException"></exception>
public Leaderboard LoadLeaderboard()
{ {
return serializer.ReadObject(reader) as Leaderboard ?? throw new InvalidOperationException(); var serializer = new DataContractJsonSerializer(typeof(Leaderboard));
using (Stream reader = File.OpenRead("Leaderboard.json"))
{
return serializer.ReadObject(reader) as Leaderboard ?? throw new InvalidOperationException();
}
} }
} }
} }

@ -12,6 +12,9 @@ using System.Threading.Tasks;
namespace QwirkleClassLibrary.Players namespace QwirkleClassLibrary.Players
{ {
/// <summary>
/// The purpose of this class is to save data at the end of a game so players can consult it later, comparing their best scores along their games.
/// </summary>
[DataContract] [DataContract]
public class Leaderboard : INotifyPropertyChanged public class Leaderboard : INotifyPropertyChanged
{ {

@ -12,6 +12,9 @@ using QwirkleClassLibrary.Tiles;
namespace QwirkleClassLibrary.Players namespace QwirkleClassLibrary.Players
{ {
/// <summary>
/// This class is mainly used to manage the sets of tiles of players during the game.
/// </summary>
[DataContract] [DataContract]
public class Player : INotifyPropertyChanged public class Player : INotifyPropertyChanged
{ {

@ -9,6 +9,9 @@ using System.Threading.Tasks;
namespace QwirkleClassLibrary.Players namespace QwirkleClassLibrary.Players
{ {
/// <summary>
/// The main purpose of this class is to save the data of the scores during the games, allowing the app to back up data.
/// </summary>
[DataContract] [DataContract]
public class Score public class Score
{ {

@ -8,8 +8,11 @@ using System.Threading.Tasks;
namespace QwirkleClassLibrary.Tiles namespace QwirkleClassLibrary.Tiles
{ {
/// <summary>
/// This is the class for the Tile, it defines what it is and what can be done with it.
/// </summary>
[DataContract] [DataContract]
public class Tile public class Tile : IComparable
{ {
[DataMember] [DataMember]
private readonly Shape shape; private readonly Shape shape;
@ -41,19 +44,13 @@ namespace QwirkleClassLibrary.Tiles
/// A getter for the shape of the Tile. /// A getter for the shape of the Tile.
/// </summary> /// </summary>
/// <returns>The shape attribute of the Tile.</returns> /// <returns>The shape attribute of the Tile.</returns>
public Shape GetShape public Shape GetShape => shape;
{
get { return shape; }
}
/// <summary> /// <summary>
/// A getter for the color of the Tile. /// A getter for the color of the Tile.
/// </summary> /// </summary>
/// <returns>The color attribute of the Tile.</returns> /// <returns>The color attribute of the Tile.</returns>
public Color GetColor public Color GetColor => color;
{
get { return color; }
}
/// <summary> /// <summary>
/// This method is used to override the ToString() method. It is simply a tool to facilitate the development. /// This method is used to override the ToString() method. It is simply a tool to facilitate the development.
@ -63,5 +60,69 @@ namespace QwirkleClassLibrary.Tiles
{ {
return color.ToString() + " " + shape.ToString(); return color.ToString() + " " + shape.ToString();
} }
public int CompareTo(object? obj)
{
if (obj == null)
{
return 1;
}
var otherTile = obj as Tile;
if (color == otherTile!.color)
{
return shape.CompareTo(otherTile.shape);
}
return color.CompareTo(otherTile.color);
}
public override bool Equals(object? obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
var otherTile = obj as Tile;
return color == otherTile!.color && shape == otherTile.shape;
}
public override int GetHashCode()
{
return HashCode.Combine(color, shape);
}
public static bool operator ==(Tile tile1, Tile tile2)
{
return EqualityComparer<Tile>.Default.Equals(tile1, tile2);
}
public static bool operator !=(Tile tile1, Tile tile2)
{
return !(tile1 == tile2);
}
public static bool operator <(Tile tile1, Tile tile2)
{
return tile1.CompareTo(tile2) < 0;
}
public static bool operator >(Tile tile1, Tile tile2)
{
return tile1.CompareTo(tile2) > 0;
}
public static bool operator <=(Tile tile1, Tile tile2)
{
return tile1.CompareTo(tile2) <= 0;
}
public static bool operator >=(Tile tile1, Tile tile2)
{
return tile1.CompareTo(tile2) >= 0;
}
} }
} }

@ -8,6 +8,9 @@ using System.Threading.Tasks;
namespace QwirkleClassLibrary.Tiles namespace QwirkleClassLibrary.Tiles
{ {
/// <summary>
/// This class is used during the game for the tile redistribution system.
/// </summary>
[DataContract] [DataContract]
public class TileBag public class TileBag
{ {

@ -19,10 +19,10 @@ namespace Qwirkle
InitializeComponent(); InitializeComponent();
//MainPage = new NavigationPage(new MainPage());
MainPage = new AppShell(); MainPage = new AppShell();
Routing.RegisterRoute(nameof(SetPlayers), typeof(SetPlayers)); Routing.RegisterRoute(nameof(SetPlayers), typeof(SetPlayers));
Routing.RegisterRoute(nameof(Settings), typeof(Settings));
Routing.RegisterRoute(nameof(Gameboard), typeof(Gameboard)); Routing.RegisterRoute(nameof(Gameboard), typeof(Gameboard));
Routing.RegisterRoute(nameof(Rules), typeof(Rules)); Routing.RegisterRoute(nameof(Rules), typeof(Rules));
Routing.RegisterRoute(nameof(MainPage), typeof(MainPage)); Routing.RegisterRoute(nameof(MainPage), typeof(MainPage));

@ -21,6 +21,7 @@ namespace Qwirkle
DisplayAlert("Game notification", "Enter minimun 2 player and max 4 player !", "Ok ! Lets's go !"); DisplayAlert("Game notification", "Enter minimun 2 player and max 4 player !", "Ok ! Lets's go !");
Shell.Current.GoToAsync("SetPlayers"); Shell.Current.GoToAsync("SetPlayers");
//Navigation.PushAsync(new SetPlayers());
} }
public async void OnContinueClicked(object sender, EventArgs e) public async void OnContinueClicked(object sender, EventArgs e)
@ -29,7 +30,8 @@ namespace Qwirkle
try try
{ {
((App)Application.Current!).Game = gameLoad.LoadGame(); ((App)Application.Current!).Game = gameLoad.LoadGame();
await Navigation.PushAsync(new Gameboard()); await Shell.Current.GoToAsync("Gameboard");
//await Navigation.PushAsync(new Gameboard());
} }
catch catch
{ {

@ -1,5 +1,7 @@
using CommunityToolkit.Maui; using CommunityToolkit.Maui;
using CommunityToolkit.Maui.Views;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Maui;
namespace Qwirkle namespace Qwirkle
{ {
@ -11,6 +13,7 @@ namespace Qwirkle
builder builder
.UseMauiApp<App>() .UseMauiApp<App>()
.UseMauiCommunityToolkit() .UseMauiCommunityToolkit()
.UseMauiCommunityToolkitMediaElement()
.ConfigureFonts(fonts => .ConfigureFonts(fonts =>
{ {
fonts.AddFont("DiloWorld.ttf", "DiloWorld"); fonts.AddFont("DiloWorld.ttf", "DiloWorld");
@ -18,7 +21,7 @@ namespace Qwirkle
}); });
#if DEBUG #if DEBUG
builder.Logging.AddDebug(); builder.Logging.AddDebug();
#endif #endif

@ -11,6 +11,7 @@ using Color = Microsoft.Maui.Graphics.Color;
using System.Drawing; using System.Drawing;
using QwirkleClassLibrary.Persistences; using QwirkleClassLibrary.Persistences;
using CommunityToolkit.Maui.Views; using CommunityToolkit.Maui.Views;
using System.Diagnostics;
namespace Qwirkle.Pages; namespace Qwirkle.Pages;
@ -108,8 +109,6 @@ public partial class Gameboard : ContentPage
{ {
var x = game.GetPlayerScore(game.GetPlayingPlayer(), game.CellsUsed, game.GetBoard()!); var x = game.GetPlayerScore(game.GetPlayingPlayer(), game.CellsUsed, game.GetBoard()!);
DisplayAlert("TEMP ALERT FOR DEBUG", game.GetPlayingPlayer().ToString() + " win : " + x, "Copy !");
game.EmptyCellUsed(); game.EmptyCellUsed();
game.DrawTiles(game.GetPlayingPlayer()); game.DrawTiles(game.GetPlayingPlayer());
} }
@ -191,7 +190,7 @@ public partial class Gameboard : ContentPage
private void Game_NextPlayerNotified(object? sender, NextPlayerNotifiedEventArgs args) private void Game_NextPlayerNotified(object? sender, NextPlayerNotifiedEventArgs args)
{ {
DisplayAlert("Player switch !", "It's your turn : " + args.Player.NameTag, "<3"); Debug.WriteLine(args.Player.NameTag);
} }
private void OnButtonSwapClicked(object sender, EventArgs e) private void OnButtonSwapClicked(object sender, EventArgs e)
@ -202,13 +201,17 @@ public partial class Gameboard : ContentPage
{ {
_ = AnswerSwap(); _ = AnswerSwap();
} }
else
{
DisplayAlert("Swap system", "You cannot place tiles and swap in the same turn.", "Ok !");
}
game.PlaceTileNotified -= Game_PlaceTileNotified; game.PlaceTileNotified -= Game_PlaceTileNotified;
} }
private async Task AnswerSwap() private async Task AnswerSwap()
{ {
bool answer = await DisplayAlert("Swap System", "Etes vous sur de vouloir swap vos tuiles ? Attention, si vous swapez vous ne pouvez pu jouer", "Yes", "No"); bool answer = await DisplayAlert("Swap System", "Are you sure you want to swap your tiles? Attention, if you swap you can not play", "Yes", "No");
if (answer) if (answer)
{ {
@ -236,17 +239,24 @@ public partial class Gameboard : ContentPage
if (answer) if (answer)
{ {
IGamePersistence gameIntermediateSave = new GamePersistenceXml();
gameIntermediateSave.SaveGame(game);
game.ClearGame();
await Navigation.PopToRootAsync(); await Navigation.PopToRootAsync();
} }
} }
private void OnButtonBookClicked(object? sender, EventArgs e) private void OnButtonBookClicked(object? sender, EventArgs e)
{ {
Shell.Current.GoToAsync("Rules"); Navigation.PushAsync(new Rules());
} }
private void OnButtonSettingsClicked(object? sender, EventArgs e) private void OnButtonSettingsClicked(object? sender, EventArgs e)
{ {
Shell.Current.GoToAsync("Settings"); Navigation.PushAsync(new Settings());
} }

@ -4,7 +4,15 @@
xmlns:controls="clr-namespace:Qwirkle.Views" xmlns:controls="clr-namespace:Qwirkle.Views"
Title="Gameboard" Title="Gameboard"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Name="root"> x:Name="root"
>
<Shell.BackButtonBehavior>
<BackButtonBehavior IsVisible="False" IsEnabled="False"></BackButtonBehavior>
</Shell.BackButtonBehavior>
<ContentPage.Resources> <ContentPage.Resources>
<x:Double x:Key="CellWidth">75</x:Double> <x:Double x:Key="CellWidth">75</x:Double>
@ -26,7 +34,6 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Border WidthRequest="80" HeightRequest="80" <Border WidthRequest="80" HeightRequest="80"
BackgroundColor="Transparent" BackgroundColor="Transparent"
Margin="0"> Margin="0">
@ -35,7 +42,7 @@
DropCommand="{Binding OnDropB, Source={x:Reference root}}" /> DropCommand="{Binding OnDropB, Source={x:Reference root}}" />
</Border.GestureRecognizers> </Border.GestureRecognizers>
<Image Source="bag.png" <Image Source="bag.png"
ToolTipProperties.Text="Pour m'utiliser il faut cliquer sur le boutton swap avant ^^"> ToolTipProperties.Text="To use me, you need to click the swap button first ^^">
</Image> </Image>
</Border> </Border>
@ -73,7 +80,7 @@
ToolTipProperties.Text="Click here to exit ;)" ToolTipProperties.Text="Click here to exit ;)"
Style="{StaticResource GameButton}" /> Style="{StaticResource GameButton}" />
<Label HorizontalOptions="Center" Grid.Row="0" Grid.Column="1" Text="{Binding PlayerList[0].NameTag}"></Label> <Label HorizontalOptions="Center" Grid.Row="0" Grid.Column="1" FontSize="Medium" Text="{Binding PlayerList[0].NameTag}"></Label>
<CollectionView Grid.Row="0" Grid.Column="1" ItemsSource="{Binding PlayerList[0].Tiles}" <CollectionView Grid.Row="0" Grid.Column="1" ItemsSource="{Binding PlayerList[0].Tiles}"
HorizontalOptions="Center" HorizontalOptions="Center"
VerticalOptions="Center"> VerticalOptions="Center">
@ -97,7 +104,7 @@
</CollectionView> </CollectionView>
<Label HorizontalOptions="Center" Grid.Row="1" Grid.Column="0" Text="{Binding PlayerList[2].NameTag}"></Label> <Label HorizontalOptions="Center" Grid.Row="1" Grid.Column="0" FontSize="Medium" Text="{Binding PlayerList[2].NameTag}"></Label>
<CollectionView Grid.Row="1" Grid.Column="0" ItemsSource="{Binding PlayerList[2].Tiles}" <CollectionView Grid.Row="1" Grid.Column="0" ItemsSource="{Binding PlayerList[2].Tiles}"
HorizontalOptions="Center" HorizontalOptions="Center"
VerticalOptions="Center"> VerticalOptions="Center">
@ -118,7 +125,7 @@
</CollectionView.ItemTemplate> </CollectionView.ItemTemplate>
</CollectionView> </CollectionView>
<Label HorizontalOptions="Center" Grid.Row="1" Grid.Column="2" Text="{Binding PlayerList[3].NameTag}"></Label> <Label HorizontalOptions="Center" Grid.Row="1" Grid.Column="2" FontSize="Medium" Text="{Binding PlayerList[3].NameTag}"></Label>
<CollectionView Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PlayerList[3].Tiles}" <CollectionView Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PlayerList[3].Tiles}"
HorizontalOptions="Center" HorizontalOptions="Center"
VerticalOptions="Center"> VerticalOptions="Center">
@ -139,7 +146,7 @@
</CollectionView.ItemTemplate> </CollectionView.ItemTemplate>
</CollectionView> </CollectionView>
<Label HorizontalOptions="Center" VerticalOptions="End" Grid.Row="2" Grid.Column="1" Text="{Binding PlayerList[1].NameTag}"></Label> <Label HorizontalOptions="Center" VerticalOptions="End" Grid.Row="2" Grid.Column="1" FontSize="Medium" Text="{Binding PlayerList[1].NameTag}"></Label>
<CollectionView Grid.Row="2" Grid.Column="1" ItemsSource="{Binding PlayerList[1].Tiles}" <CollectionView Grid.Row="2" Grid.Column="1" ItemsSource="{Binding PlayerList[1].Tiles}"
HorizontalOptions="Center" HorizontalOptions="Center"
VerticalOptions="Start"> VerticalOptions="Start">

@ -55,6 +55,7 @@ public partial class SetPlayers : ContentPage
game.SetNextPlayer(); game.SetNextPlayer();
Shell.Current.GoToAsync("Gameboard"); Shell.Current.GoToAsync("Gameboard");
} }
game.PlayerAddNotified -= Game_PlayerAddNotified; game.PlayerAddNotified -= Game_PlayerAddNotified;

@ -6,9 +6,4 @@ public partial class Settings : ContentPage
{ {
InitializeComponent(); InitializeComponent();
} }
public void OnGoBackClicked(object sender, EventArgs e)
{
Navigation.PopAsync();
}
} }

@ -5,6 +5,7 @@
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Name="root" x:Name="root"
CanBeDismissedByTappingOutsideOfPopup="False" CanBeDismissedByTappingOutsideOfPopup="False"
xmlns:controls="clr-namespace:Qwirkle.Views"
> >
<VerticalStackLayout HeightRequest="400" WidthRequest="500"> <VerticalStackLayout HeightRequest="400" WidthRequest="500">
@ -19,35 +20,7 @@
<Label Text="{Binding ScoreboardList[0].Key, Source={x:Reference root}}" Style="{StaticResource SuperTitle}" TextColor="HotPink" FontSize="Medium"></Label> <Label Text="{Binding ScoreboardList[0].Key, Source={x:Reference root}}" Style="{StaticResource SuperTitle}" TextColor="HotPink" FontSize="Medium"></Label>
<CollectionView ItemsSource="{Binding ScoreboardList}" BindingContext="{x:Reference root}"> <controls:Scoreboard></controls:Scoreboard>
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Grid ColumnDefinitions="4*, auto, 2*"
RowDefinitions="50">
<Label
Grid.Column="0"
Text="{Binding Key}"
Style="{StaticResource ContentTab}"/>
<Rectangle
Style="{StaticResource RectangleTab}"
Grid.Column="1"/>
<Label
Grid.Column="2"
Text="{Binding Value}"
Style="{StaticResource ContentTab}"/>
</Grid>
<Rectangle/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Button Text="Skip" Style="{StaticResource GameButton}" Clicked="OnButtonNextClick"></Button> <Button Text="Skip" Style="{StaticResource GameButton}" Clicked="OnButtonNextClick"></Button>

@ -1,4 +1,5 @@
using CommunityToolkit.Maui.Views; using CommunityToolkit.Maui.Views;
using Microsoft.Maui.Controls;
using QwirkleClassLibrary.Games; using QwirkleClassLibrary.Games;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@ -32,7 +33,8 @@ public partial class PopUpEndGame : Popup
public async void OnButtonNextClick(object sender, EventArgs e) public async void OnButtonNextClick(object sender, EventArgs e)
{ {
await CloseAsync(); Close();
await Shell.Current.GoToAsync("MainPage");
} }
} }

@ -1,7 +1,7 @@
{ {
"profiles": { "profiles": {
"Windows Machine": { "Windows Machine": {
"commandName": "MsixPackage", "commandName": "Project",
"nativeDebugging": false "nativeDebugging": false
} }
} }

@ -1,8 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> <TargetFrameworks>net8.0-windows10.0.19041.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET --> <!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> --> <!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
@ -30,6 +29,9 @@
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion> <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion> <ApplicationVersion>1</ApplicationVersion>
<WindowsPackageType Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">None</WindowsPackageType>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
@ -41,7 +43,7 @@
<ItemGroup> <ItemGroup>
<!-- App Icon --> <!-- App Icon -->
<!-- <MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />--> <!-- <MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />-->
<MauiIcon Include="Resources\AppIcon\appicon.svg"/> <MauiIcon Include="Resources\AppIcon\appicon.svg" />
<!-- Splash Screen --> <!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" /> <MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
@ -65,10 +67,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Maui" Version="9.0.0" /> <PackageReference Include="CommunityToolkit.Maui" Version="9.0.1" />
<PackageReference Include="CommunityToolkit.Maui.MediaElement" Version="3.1.1" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.40" /> <PackageReference Include="Microsoft.Maui.Controls" Version="8.0.40" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.40" /> <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.40" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.0-preview.4.24266.19" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -1,3 +1,5 @@
using Qwirkle.Pages;
namespace Qwirkle.Views; namespace Qwirkle.Views;
public partial class GoBack : ContentView public partial class GoBack : ContentView

@ -287,25 +287,6 @@ public class TestGame
Assert.False(game.PlaceTile(game.GetPlayingPlayer(), game.PlayerList[game.GetPlayingPlayerPosition()].Tiles[0], -5, 1)); Assert.False(game.PlaceTile(game.GetPlayingPlayer(), game.PlayerList[game.GetPlayingPlayerPosition()].Tiles[0], -5, 1));
} }
// [Fact]
// public void Test_DrawTile()
// {
// Game game = new Game();
// game.AddPlayerInGame("Test1");
// game.AddPlayerInGame(playerstest);
//
// game.StartGame();
// game.SetNextPlayer();
//
// Assert.True(game.DrawTiles(game.GetPlayingPlayer()));
//
// /*test*/
// TileBag bag = new TileBag(0);
// Assert.False(game.DrawTiles(game.GetPlayingPlayer()));
// return;
//
// }
[Theory] [Theory]
[InlineData(true)] [InlineData(true)]
[InlineData(false)] [InlineData(false)]
@ -342,6 +323,48 @@ public class TestGame
Assert.Equal(p, events.Player); Assert.Equal(p, events.Player);
} }
[Fact]
public void Test_IsTilePlacementCorrect()
{
var game = new Game();
var board = new Board(17, 14);
var tile = new Tile(Shape.Club, Color.Red);
board.AddTileInCell(0, 1, new Tile(Shape.Club, Color.Blue));
var x = 1;
var y = 1;
var surroundingCells = new List<Cell?>
{
board.GetCell(x + 1, y),
board.GetCell(x - 1, y),
board.GetCell(x, y + 1),
board.GetCell(x, y - 1)
};
bool result = game.IsTilePlacementCorrect(tile, x, y, board, surroundingCells);
Assert.True(result);
}
[Fact]
public void Test_IsMoveCorrect()
{
var game = new Game();
var board = new Board(17, 14);
var tile = new Tile(Shape.Club, Color.Red);
board.AddTileInCell(0, 1, new Tile(Shape.Club, Color.Blue));
var x = 2;
var y = 1;
bool result = game.IsMoveCorrect(tile, x, y, board);
Assert.False(result);
}
[Fact] [Fact]
public void Test_IsMoveCorrectSixLine() public void Test_IsMoveCorrectSixLine()
{ {
@ -381,6 +404,41 @@ public class TestGame
} }
[Fact]
public void Test_CheckTileInCompletedLines()
{
int nbTiles = 0;
var checkdoubles = new List<Tile>()
{
new(Shape.Club, Color.Blue),
new(Shape.Club, Color.Red),
new(Shape.Club, Color.Green),
};
var t1 = new Tile(Shape.Club, Color.Green);
Assert.False(Game.CheckTileInCompletedLines(t1, ref nbTiles, ref checkdoubles));
}
[Fact]
public void Test_CheckWrongCompletedLines()
{
var board = new Board(17, 14);
var checkDoubles = new List<Tile>();
int x = 4, y = 1, dx = 1, dy = 0;
board.AddTileInCell(1, 1, new Tile(Shape.Club, Color.Red));
board.AddTileInCell(2, 1, new Tile(Shape.Square, Color.Red));
board.AddTileInCell(3, 1, new Tile(Shape.Star, Color.Red));
board.AddTileInCell(5, 1, new Tile(Shape.Round, Color.Red));
board.AddTileInCell(6, 1, new Tile(Shape.Shuriken, Color.Red));
board.AddTileInCell(7, 1, new Tile(Shape.Rhombus, Color.Red));
bool result = Game.CheckWrongCompletedLines(x, y, dx, dy, board, ref checkDoubles);
Assert.False(result);
}
[Theory] [Theory]
[InlineData(3, 1, 4, 1, 5, 1, 5)] [InlineData(3, 1, 4, 1, 5, 1, 5)]
[InlineData(2, 2, 3, 2, 4, 2, 5)] [InlineData(2, 2, 3, 2, 4, 2, 5)]
@ -389,7 +447,7 @@ public class TestGame
{ {
var game = new Game(); var game = new Game();
var player = new Player("TestPlayer"); var player = new Player("TestPlayer");
var board = new Board(8, 8); var board = game.GetBoard();
board.AddTileInCell(1, 1, new Tile(Shape.Club, Color.Red)); board.AddTileInCell(1, 1, new Tile(Shape.Club, Color.Red));
board.AddTileInCell(2, 1, new Tile(Shape.Square, Color.Red)); board.AddTileInCell(2, 1, new Tile(Shape.Square, Color.Red));
@ -414,6 +472,14 @@ public class TestGame
Assert.Equal(expectedScore, score); Assert.Equal(expectedScore, score);
} }
[Theory]
[InlineData(0, -1, -1, 6)]
[InlineData(1, 0, 4, -1)]
public void Test_ShouldIncreaseScore(int dx, int dy, int cellsX, int cellsY)
{
Assert.True(Game.ShouldIncreaseScore(new Tuple<int, int>(dx, dy), new Tuple<int, int>(cellsX, cellsY)));
}
[Fact] [Fact]
public void Test_EndOfGame() public void Test_EndOfGame()
{ {

Binary file not shown.

@ -1,4 +1,3 @@
[![Build Status](https://codefirst.iut.uca.fr/api/badges/jeremy.mouyon/sae201_qwirkle/status.svg)](https://codefirst.iut.uca.fr/jeremy.mouyon/sae201_qwirkle) [![Build Status](https://codefirst.iut.uca.fr/api/badges/jeremy.mouyon/sae201_qwirkle/status.svg)](https://codefirst.iut.uca.fr/jeremy.mouyon/sae201_qwirkle)
# Notre QWIRKLE # Notre QWIRKLE
@ -9,7 +8,11 @@ Projet .NET MAUI portant sur la conception d'un jeu vidéo en reprenant un jeu d
## Présentation de notre jeu ## Présentation de notre jeu
Notré présentation est consutable sur ce [lien](https://codefirst.iut.uca.fr/git/jeremy.mouyon/sae201_qwirkle/wiki/Pr%C3%A9sentation-du-jeu). Notre présentation est consutable sur ce [lien](https://codefirst.iut.uca.fr/git/jeremy.mouyon/sae201_qwirkle/wiki/Pr%C3%A9sentation-du-jeu).
### Vidéo de présentation
Voici le [lien](https://opencast.dsi.uca.fr/paella/ui/watch.html?id=4c3f5f66-69e8-4700-878e-3ec700e14640) de notre vidéo de présentation du jeu
## Composition du groupe ## Composition du groupe
@ -20,6 +23,17 @@ Notré présentation est consutable sur ce [lien](https://codefirst.iut.uca.fr/g
## *Comment utiliser notre application ?* ## *Comment utiliser notre application ?*
### Executable
Semé d'embûches, le chemin pour vous fournir un exécutable a finalement été trouvé. Il se trouve à la racine de notre projet.
Vous pourrez via celui-ci, utiliser notre application de A à Z !
*Nous ne sommes malheureusement pas en mesure de vous fournir un fichier .msix!*
### Via un IDE
Tout d'abord, récupérez notre projet en tapant dans votre terminal : Tout d'abord, récupérez notre projet en tapant dans votre terminal :
```sh ```sh
git clone https://codefirst.iut.uca.fr/git/jeremy.mouyon/sae201_qwirkle.git git clone https://codefirst.iut.uca.fr/git/jeremy.mouyon/sae201_qwirkle.git
@ -53,9 +67,10 @@ Après cela, vous pourrez continuer à placer d'autres tuiles, si vous le souhai
Si vous souhaitez échanger des des tuiles de votre main, vous pouvez choisir de les échanger avec des tuiles de la pioche. Entrez simplement les numéros correspondants des tuiles que vous voulez échanger. Si vous souhaitez échanger des des tuiles de votre main, vous pouvez choisir de les échanger avec des tuiles de la pioche. Entrez simplement les numéros correspondants des tuiles que vous voulez échanger.
Attention, gardez à l'esprit que si vous choisissez cette option, vous ne pourrez pas jouer durant ce tour. Attention, gardez à l'esprit que si vous choisissez cette option, vous ne pourrez pas jouer durant ce tour.
### Et voilà ! ## Where is the sound ?
Vous vous demandez probablement où est passé le son ? Il n'y en a finalement pas eu. Le temps d'implémenter un système sonore avec un réglage était impossible dans les délais fixés. Nous avons quand même fait la page, conformément à notre conception !
Votre partie est lancée ! Amusez-vous bien avec ce jeu de Qwirkle ! ## Et voilà !
*PS pour Mr Chargueraud : Promesse faite, promesse tenue ! Mon problème de score est en grande partie réglé (non sans peine) !* Votre partie est lancée ! Amusez-vous bien avec ce jeu de Qwirkle ! :)
*Le seul problème restant étant que sur certains cas, le +6 de la complétion de ligne peut s'effectuer 2 fois.*
Loading…
Cancel
Save