#region license // This file is part of Vocaluxe. // // Vocaluxe is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Vocaluxe is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Vocaluxe. If not, see . #endregion using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using VocaluxeLib.Menu; using VocaluxeLib.Utils; [assembly: ComVisible(false)] namespace VocaluxeLib.PartyModes.Challenge { public class CResultTableRow : IComparable { public int Position; public Guid PlayerId; public int NumPlayed; public int NumWon; public int NumSingPoints; public int NumGamePoints; public int CompareTo(object obj) { var row = obj as CResultTableRow; if (row != null) { var res = row.NumGamePoints.CompareTo(NumGamePoints); if (res == 0) { res = row.NumSingPoints.CompareTo(NumSingPoints); if (res == 0) { res = row.NumWon.CompareTo(NumWon); } } return res; } throw new ArgumentException("object is not a ResultTableRow"); } } public abstract class CPartyScreenChallenge : CMenuParty { protected new CPartyModeChallenge _PartyMode; public override void Init() { base.Init(); _PartyMode = (CPartyModeChallenge)base._PartyMode; } } // ReSharper disable ClassNeverInstantiated.Global public sealed class CPartyModeChallenge : CPartyMode // ReSharper restore ClassNeverInstantiated.Global { public override int MinMics { get { return 1; } } public override int MaxMics { get { return CBase.Config.GetMaxNumMics(); } } public override int MinPlayers { get { return 1; } } public override int MaxPlayers { get { return 16; } } public override int MinTeams { get { return 1; } } public override int MaxTeams { get { return 1; } } public override int MinPlayersPerTeam { get { return MinPlayers; } } public override int MaxPlayersPerTeam { get { return MaxPlayers; } } private enum EStage { Config, Songs, Names, Main, SongSelection, Singing, MedleySinging } public struct SData { public int NumPlayer; public int NumPlayerAtOnce; public int NumRounds; public int SongsPerPlayer; public int NumJokers; public bool RefillJokers; public bool RememberSongs; public int[] Jokers; public List ProfileIds; public ESongSource SongSource; public ESongSorting Sorting; public int CategoryIndex; public int PlaylistId; public EGameMode GameMode; public int NumMedleySongs; public CChallengeRounds Rounds; public List ResultTable; public int[,] Results; public int CurrentRoundNr; public List Songs; } private struct SStats { public Guid ProfileId; public int SingPoints; public int GamePoints; public int Won; } public SData GameData; private EStage _Stage; private readonly HashSet _SungSongs = new HashSet(); public CPartyModeChallenge(int id) : base(id) { _ScreenSongOptions.Selection.RandomOnly = false; _ScreenSongOptions.Selection.PartyMode = true; _ScreenSongOptions.Selection.CategoryChangeAllowed = true; _ScreenSongOptions.Selection.NumJokers = new int[] { 5, 5 }; _ScreenSongOptions.Selection.TeamNames = new string[] { "foo", "bar" }; _ScreenSongOptions.Sorting.SearchString = string.Empty; _ScreenSongOptions.Sorting.SearchActive = false; _ScreenSongOptions.Sorting.DuetOptions = EDuetOptions.NoDuets; _ScreenSongOptions.Sorting.FilterPlaylistId = -1; GameData = new SData { NumPlayer = 4, NumPlayerAtOnce = 2, NumRounds = 6, SongsPerPlayer = 3, NumJokers = 5, RefillJokers = true, RememberSongs = true, CurrentRoundNr = 1, ProfileIds = new List(), Sorting = CBase.Config.GetSongSorting(), SongSource = ESongSource.TR_SONGSOURCE_ALLSONGS, PlaylistId = 0, CategoryIndex = 0, GameMode = EGameMode.TR_GAMEMODE_NORMAL, NumMedleySongs = 5, Results = null, Songs = new List() }; } public override void SetDefaults() { _Stage = EStage.Config; _ScreenSongOptions.Sorting.IgnoreArticles = CBase.Config.GetIgnoreArticles(); _ScreenSongOptions.Sorting.SongSorting = CBase.Config.GetSongSorting(); _ScreenSongOptions.Sorting.Tabs = EOffOn.TR_CONFIG_OFF; _ScreenSongOptions.Selection.SongIndex = -1; _ScreenSongOptions.Selection.CategoryIndex = -1; if (CBase.Config.GetTabs() == EOffOn.TR_CONFIG_ON && _ScreenSongOptions.Sorting.SongSorting != ESongSorting.TR_CONFIG_NONE) { _ScreenSongOptions.Sorting.Tabs = EOffOn.TR_CONFIG_ON; } GameData.ResultTable = new List(); } public override bool Init() { if (!base.Init()) { return false; } SetDefaults(); return true; } public override void UpdateGame() { } private IMenu _GetNextScreen() { switch (_Stage) { case EStage.Config: return _Screens["CPartyScreenChallengeConfig"]; case EStage.Songs: return _Screens["CPartyScreenChallengeSongs"]; case EStage.Names: return _Screens["CPartyScreenChallengeNames"]; case EStage.Main: return _Screens["CPartyScreenChallengeMain"]; case EStage.SongSelection: return CBase.Graphics.GetScreen(EScreen.Song); case EStage.Singing: case EStage.MedleySinging: return CBase.Graphics.GetScreen(EScreen.Sing); default: throw new ArgumentException("Invalid stage: " + _Stage); } } private void _FadeToScreen() { CBase.Graphics.FadeTo(_GetNextScreen()); } public void Next() { switch (_Stage) { case EStage.Config: _Stage = EStage.Songs; break; case EStage.Songs: _Stage = EStage.Names; break; case EStage.Names: _Stage = EStage.Main; CBase.Songs.ResetSongSung(); if (GameData.RememberSongs) { foreach (var songId in _SungSongs) { CBase.Songs.AddPartySongSung(songId); } } else { _SungSongs.Clear(); } GameData.Songs.Clear(); GameData.ResultTable = new List(); GameData.Rounds = new CChallengeRounds(GameData.NumRounds, GameData.NumPlayer, GameData.NumPlayerAtOnce); GameData.CurrentRoundNr = 1; GameData.Jokers = null; break; case EStage.Main: if (GameData.GameMode == EGameMode.TR_GAMEMODE_MEDLEY) { _Stage = EStage.MedleySinging; _PrepareMedleyRound(); } else { _Stage = EStage.SongSelection; _PrepareSongSelection(); } break; case EStage.SongSelection: _Stage = EStage.Singing; break; case EStage.MedleySinging: case EStage.Singing: _Stage = EStage.Main; _UpdateScores(); break; default: throw new ArgumentException("Invalid stage: " + _Stage); } _FadeToScreen(); } public void Back() { switch (_Stage) { case EStage.Config: CBase.Graphics.FadeTo(EScreen.Party); return; case EStage.Songs: _Stage = EStage.Config; break; case EStage.Names: _Stage = EStage.Songs; break; case EStage.Main: _Stage = EStage.Names; break; default: // Rest is not allowed throw new ArgumentException("Invalid stage: " + _Stage); } _FadeToScreen(); } public override IMenu GetStartScreen() { return _Screens["CPartyScreenChallengeConfig"]; } public override SScreenSongOptions GetScreenSongOptions() { return _ScreenSongOptions; } public override void OnSongChange(int songIndex, ref SScreenSongOptions screenSongOptions) { if (_ScreenSongOptions.Selection.SelectNextRandomSong && songIndex != -1) { _ScreenSongOptions.Selection.SelectNextRandomSong = false; } _ScreenSongOptions.Selection.SongIndex = songIndex; screenSongOptions = _ScreenSongOptions; } public override void OnCategoryChange(int categoryIndex, ref SScreenSongOptions screenSongOptions) { if (categoryIndex != -1 || CBase.Config.GetTabs() == EOffOn.TR_CONFIG_OFF) { //If category is selected or tabs off: only random song selection _ScreenSongOptions.Selection.SelectNextRandomSong = true; _ScreenSongOptions.Selection.RandomOnly = true; } else { //If no category is selected: let user choose category _ScreenSongOptions.Selection.SongIndex = -1; _ScreenSongOptions.Selection.RandomOnly = false; } _ScreenSongOptions.Selection.CategoryIndex = categoryIndex; screenSongOptions = _ScreenSongOptions; } public override void SetSearchString(string searchString, bool visible) { _ScreenSongOptions.Sorting.SearchString = searchString; _ScreenSongOptions.Sorting.SearchActive = visible; } public override void JokerUsed(int teamNr) { if (_ScreenSongOptions.Selection.NumJokers == null) { return; } if (teamNr >= _ScreenSongOptions.Selection.NumJokers.Length) { return; } if (!GameData.RefillJokers) { var round = GameData.Rounds[GameData.CurrentRoundNr - 1]; GameData.Jokers[round.Players[teamNr]]--; } _ScreenSongOptions.Selection.NumJokers[teamNr]--; _ScreenSongOptions.Selection.RandomOnly = true; _ScreenSongOptions.Selection.CategoryChangeAllowed = false; } public override void SongSelected(int songId) { _PrepareRound(new int[] { songId }); Next(); } public override void LeavingHighscore() { //Remember sung songs, so they don't will be selected a second time for (var i = 0; i < CBase.Game.GetNumSongs(); i++) { CBase.Songs.AddPartySongSung(CBase.Game.GetSong(i).Id); _SungSongs.Add(CBase.Game.GetSong(i).Id); } GameData.CurrentRoundNr++; Next(); } /// /// Start a new medley round based on song configuration /// private void _PrepareMedleyRound() { //Select songs for medley var songIds = new int[GameData.NumMedleySongs]; for (var i = 0; i < GameData.NumMedleySongs; i++) { if (GameData.Songs.Count == 0) { _UpdateSongList(); } songIds[i] = GameData.Songs[0]; GameData.Songs.RemoveAt(0); } _PrepareRound(songIds); } /// /// Setup options for song selection /// private void _PrepareSongSelection() { _ScreenSongOptions.Selection.RandomOnly = true; _ScreenSongOptions.Selection.SelectNextRandomSong = true; _ScreenSongOptions.Sorting.IgnoreArticles = CBase.Config.GetIgnoreArticles(); switch (GameData.SongSource) { case ESongSource.TR_SONGSOURCE_ALLSONGS: _ScreenSongOptions.Sorting.SongSorting = CBase.Config.GetSongSorting(); _ScreenSongOptions.Sorting.Tabs = CBase.Config.GetTabs(); _ScreenSongOptions.Sorting.FilterPlaylistId = -1; _ScreenSongOptions.Selection.SongIndex = -1; _ScreenSongOptions.Selection.CategoryIndex = -1; _ScreenSongOptions.Selection.CategoryChangeAllowed = true; break; case ESongSource.TR_SONGSOURCE_CATEGORY: _ScreenSongOptions.Sorting.SongSorting = GameData.Sorting; _ScreenSongOptions.Sorting.Tabs = EOffOn.TR_CONFIG_ON; _ScreenSongOptions.Sorting.FilterPlaylistId = -1; _ScreenSongOptions.Selection.CategoryIndex = GameData.CategoryIndex; _ScreenSongOptions.Selection.CategoryChangeAllowed = false; break; case ESongSource.TR_SONGSOURCE_PLAYLIST: _ScreenSongOptions.Sorting.SongSorting = CBase.Config.GetSongSorting(); _ScreenSongOptions.Sorting.Tabs = EOffOn.TR_CONFIG_OFF; _ScreenSongOptions.Sorting.FilterPlaylistId = GameData.PlaylistId; _ScreenSongOptions.Selection.CategoryChangeAllowed = false; break; } _SetNumJokers(); _SetTeamNames(); } /// /// Prepare next game and fill song queue based on configuration and given songs. /// /// Array of SongIds that are selected /// false, if something can't setup correctly private bool _PrepareRound(int[] songIds) { //Reset game CBase.Game.Reset(); CBase.Game.ClearSongs(); #region PlayerNames CBase.Game.SetNumPlayer(GameData.NumPlayerAtOnce); var players = CBase.Game.GetPlayers(); if (players == null || players.Length < GameData.NumPlayerAtOnce) { return false; } //Get current round var c = GameData.Rounds[GameData.CurrentRoundNr - 1]; for (var i = 0; i < GameData.NumPlayerAtOnce; i++) { //try to fill with correct player data if (c != null) { players[i].ProfileId = GameData.ProfileIds[c.Players[i]]; } else { players[i].ProfileId = Guid.Empty; } } #endregion PlayerNames #region SongQueue //Add all songs with configure game mode to song queue for (var i = 0; i < songIds.Length; i++) { CBase.Game.AddSong(songIds[i], GameData.GameMode); } #endregion SongQueue return true; } private void _SetNumJokers() { var jokers = new int[GameData.NumPlayerAtOnce]; if (GameData.RefillJokers) { for (var i = 0; i < jokers.Length; i++) { jokers[i] = GameData.NumJokers; } } else { if (GameData.Jokers == null) { GameData.Jokers = new int[GameData.NumPlayer]; for (var i = 0; i < GameData.ProfileIds.Count; i++) { GameData.Jokers[i] = GameData.NumJokers; } } var round = GameData.Rounds[GameData.CurrentRoundNr - 1]; for (var i = 0; i < GameData.NumPlayerAtOnce; i++) { jokers[i] = GameData.Jokers[round.Players[i]]; } } _ScreenSongOptions.Selection.NumJokers = jokers; } private void _SetTeamNames() { if (GameData.NumPlayerAtOnce < 1 || GameData.ProfileIds.Count < GameData.NumPlayerAtOnce) { _ScreenSongOptions.Selection.TeamNames = new string[] { "foo", "bar" }; return; } _ScreenSongOptions.Selection.TeamNames = new string[GameData.NumPlayerAtOnce]; var c = GameData.Rounds[GameData.CurrentRoundNr - 1]; for (var i = 0; i < GameData.NumPlayerAtOnce; i++) { _ScreenSongOptions.Selection.TeamNames[i] = CBase.Profiles.GetPlayerName(GameData.ProfileIds[c.Players[i]]); } } /// /// Fill song list based on song configuration. This is basically needed for singing medleys, /// because we don't show song selection screen for these games. /// private void _UpdateSongList() { if (GameData.Songs.Count > 0) { return; } switch (GameData.SongSource) { case ESongSource.TR_SONGSOURCE_PLAYLIST: for (var i = 0; i < CBase.Playlist.GetSongCount(GameData.PlaylistId); i++) { var id = CBase.Playlist.GetSong(GameData.PlaylistId, i).SongId; if (CBase.Songs.GetSongById(id).IsGameModeAvailable(EGameMode.TR_GAMEMODE_MEDLEY)) { GameData.Songs.Add(id); } } break; case ESongSource.TR_SONGSOURCE_ALLSONGS: var avSongs = CBase.Songs.GetSongs(); GameData.Songs.AddRange(avSongs.Where(song => song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_MEDLEY)).Select(song => song.Id)); break; case ESongSource.TR_SONGSOURCE_CATEGORY: //Save old sorting to roll it back after getting songs for configured category var oldSorting = CBase.Config.GetSongSorting(); CBase.Songs.SortSongs(GameData.Sorting, EOffOn.TR_CONFIG_ON, CBase.Config.GetIgnoreArticles(), string.Empty, EDuetOptions.NoDuets, -1); CBase.Songs.SetCategory(GameData.CategoryIndex); avSongs = CBase.Songs.GetVisibleSongs(); GameData.Songs.AddRange(avSongs.Where(song => song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_MEDLEY)).Select(song => song.Id)); CBase.Songs.SetCategory(-1); CBase.Songs.SortSongs(oldSorting, CBase.Config.GetTabs(), CBase.Config.GetIgnoreArticles(), string.Empty, EDuetOptions.NoDuets, -1); break; } var notSung = GameData.Songs.Where(songId => !_SungSongs.Contains(songId)).ToList(); if (notSung.Count >= GameData.NumMedleySongs) { GameData.Songs = notSung; } GameData.Songs.Shuffle(); } private void _UpdateScores() { //Prepare results table if (GameData.ResultTable.Count == 0) { for (var i = 0; i < GameData.NumPlayer; i++) { var row = new CResultTableRow { PlayerId = GameData.ProfileIds[i], NumPlayed = 0, NumWon = 0, NumSingPoints = 0, NumGamePoints = 0 }; GameData.ResultTable.Add(row); } GameData.Results = new int[GameData.NumRounds, GameData.NumPlayerAtOnce]; for (var i = 0; i < GameData.NumRounds; i++) { for (var j = 0; j < GameData.NumPlayerAtOnce; j++) { GameData.Results[i, j] = 0; } } } //Get points from game var points = CBase.Game.GetPoints(); var players = CBase.Game.GetPlayers(); //Go over all rounds and sum up points for (var round = 0; round < points.NumRounds; round++) { var res = points.GetPlayer(round, GameData.NumPlayerAtOnce); if (res == null || res.Length < GameData.NumPlayerAtOnce) { return; } for (var p = 0; p < GameData.NumPlayerAtOnce; p++) { players[p].Points += res[p].Points; players[p].PointsGoldenNotes += res[p].PointsGoldenNotes; players[p].PointsLineBonus += res[p].PointsLineBonus; } } //Calculate average points for (var p = 0; p < GameData.NumPlayerAtOnce; p++) { players[p].Points /= points.NumRounds; players[p].PointsGoldenNotes /= points.NumRounds; players[p].PointsLineBonus /= points.NumRounds; //Save points in GameData GameData.Results[GameData.CurrentRoundNr - 2, p] = (int)Math.Round(players[p].Points); } var stats = _GetPointsForPlayer(players); for (var i = 0; i < GameData.NumPlayerAtOnce; i++) { //Find matching row in results table var index = -1; for (var j = 0; j < GameData.ResultTable.Count; j++) { if (stats[i].ProfileId == GameData.ResultTable[j].PlayerId) { index = j; break; } } if (index == -1) { continue; } var row = GameData.ResultTable[index]; //Update results entry row.NumPlayed++; row.NumWon += stats[i].Won; row.NumSingPoints += stats[i].SingPoints; row.NumGamePoints += stats[i].GamePoints; GameData.ResultTable[index] = row; } GameData.ResultTable.Sort(); //Update position-number var pos = 1; var lastPoints = 0; var lastSingPoints = 0; foreach (var resultRow in GameData.ResultTable) { if (lastPoints > resultRow.NumGamePoints || lastSingPoints > resultRow.NumSingPoints) { pos++; } resultRow.Position = pos; lastPoints = resultRow.NumGamePoints; lastSingPoints = resultRow.NumSingPoints; } } private List _GetPointsForPlayer(SPlayer[] results) { var result = new List(); for (var i = 0; i < GameData.NumPlayerAtOnce; i++) { var stat = new SStats { ProfileId = results[i].ProfileId, SingPoints = (int)Math.Round(results[i].Points), Won = 0, GamePoints = 0 }; result.Add(stat); } result.Sort((s1, s2) => s1.SingPoints.CompareTo(s2.SingPoints)); var current = result[result.Count - 1].SingPoints; var points = result.Count; var wonset = false; for (var i = result.Count - 1; i >= 0; i--) { var res = result[i]; if (i < result.Count - 1) { if (current > res.SingPoints) { res.GamePoints = i * 2; wonset = true; points = res.GamePoints; } else { if (!wonset) { res.Won = 1; } res.GamePoints = points; } } else { res.GamePoints = i * 2; res.Won = 1; } current = res.SingPoints; result[i] = res; } return result; } } }