// Manages every possible moves for the current opponent after his pick. private bool OpponentAfterPick(ref Tuple <int, TilePivot, int?> kanInProgress) { if (_game.Round.IaManager.TsumoDecision(kanInProgress != null)) { InvokeOverlay("Tsumo", _game.Round.CurrentPlayerIndex); return(true); } Tuple <int, TilePivot> opponentWithKanTilePick = _game.Round.IaManager.KanDecision(true); if (opponentWithKanTilePick != null) { TilePivot compensationTile = OpponentBeginCallKan(_game.Round.CurrentPlayerIndex, opponentWithKanTilePick.Item2, true, kanInProgress != null); kanInProgress = new Tuple <int, TilePivot, int?>(_game.Round.CurrentPlayerIndex, compensationTile, null); return(false); } kanInProgress = null; TilePivot riichiTile = _game.Round.IaManager.RiichiDecision(); if (riichiTile != null) { CallRiichi(riichiTile); return(false); } Discard(_game.Round.IaManager.DiscardDecision()); return(false); }
// Clears and refills the hand panel of the specified player index. private void FillHandPanel(int pIndex, TilePivot pickTile = null) { bool isHuman = pIndex == GamePivot.HUMAN_INDEX; Panel panel = this.FindPanel("StpHandP", pIndex); this.FindPanel("StpPickP", pIndex).Children.Clear(); panel.Children.Clear(); foreach (TilePivot tile in _game.Round.GetHand(pIndex).ConcealedTiles) { if (pickTile == null || !ReferenceEquals(pickTile, tile)) { panel.Children.Add(tile.GenerateTileButton(isHuman && !_game.Round.IsRiichi(pIndex) ? BtnDiscard_Click : (RoutedEventHandler)null, (AnglePivot)pIndex, !isHuman && !Properties.Settings.Default.DebugMode)); } } if (pickTile != null) { this.FindPanel("StpPickP", pIndex).Children.Add( pickTile.GenerateTileButton( _game.Round.IsHumanPlayer ? BtnDiscard_Click : (RoutedEventHandler)null, (AnglePivot)pIndex, !_game.Round.IsHumanPlayer && !Properties.Settings.Default.DebugMode ) ); } }
/// <summary> /// Extension; generates a button which represents a tile. /// </summary> /// <param name="tile">The tile to display.</param> /// <param name="handler">Optionnal; event on click on the button; default value is <c>Null</c>.</param> /// <param name="angle">Optionnal; rotation angle; default is <c>0°</c>.</param> /// <param name="concealed">Optionnal; set <c>True</c> to display a concealed tile; default is <c>False</c>.</param> /// <returns>A button representing the tile.</returns> internal static Button GenerateTileButton(this TilePivot tile, RoutedEventHandler handler = null, AnglePivot angle = AnglePivot.A0, bool concealed = false) { string rscName = concealed ? CONCEALED_TILE_RSC_NAME : tile.ToString(); Bitmap tileBitmap = Properties.Resources.ResourceManager.GetObject(rscName) as Bitmap; var button = new Button { Height = angle == AnglePivot.A0 || angle == AnglePivot.A180 ? TILE_HEIGHT : TILE_WIDTH, Width = angle == AnglePivot.A0 || angle == AnglePivot.A180 ? TILE_WIDTH : TILE_HEIGHT, Content = new System.Windows.Controls.Image { Source = tileBitmap.ToBitmapImage(), LayoutTransform = new RotateTransform(Convert.ToDouble(angle.ToString().Replace("A", string.Empty))) }, Tag = tile }; if (handler != null) { button.Click += handler; } return(button); }
internal Substitution(TilePivot subbed, TilePivot subber, IEnumerable <TilePivot> subSource) { Subbed = subbed; Subber = subber; SubSource = subSource.ToList(); Probability = SubSource.Count(t => t.Equals(Subber)) / (double)SubSource.Count; }
public void IsComplete_WithDeclaredKan_ComplexMultiplePatterns() { List <TilePivot> tilesSet = TilePivot.GetCompleteSet(false); var concealedTiles = new List <TilePivot> { TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 1), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 1), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 2), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 2), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 3), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 3), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 4), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 4), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 4), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 5), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 6) }; concealedTiles = concealedTiles.OrderBy(t => GlobalTools.Randomizer.NextDouble()).ToList(); List <List <TileComboPivot> > result = HandPivot.IsCompleteBasic(concealedTiles, new List <TileComboPivot> { TileComboPivot.BuildSquare(TilePivot.GetTile(tilesSet, FamilyPivot.Dragon, dragon: DragonPivot.White)) }); Assert.IsNotNull(result); Assert.AreEqual(2, result.Count); result.ForEach(cg => AssertFiveCombinationsIncludingOnePair(cg)); }
public void IsComplete_FullyConcealed_AverageSinglePattern() { List <TilePivot> tilesSet = TilePivot.GetCompleteSet(false); var concealedTiles = new List <TilePivot> { TilePivot.GetTile(tilesSet, FamilyPivot.Wind, wind: WindPivot.North), TilePivot.GetTile(tilesSet, FamilyPivot.Wind, wind: WindPivot.North), TilePivot.GetTile(tilesSet, FamilyPivot.Dragon, dragon: DragonPivot.Red), TilePivot.GetTile(tilesSet, FamilyPivot.Dragon, dragon: DragonPivot.Red), TilePivot.GetTile(tilesSet, FamilyPivot.Dragon, dragon: DragonPivot.Red), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 1), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 1), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 1), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 2), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 2), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 3), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 3), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 4), TilePivot.GetTile(tilesSet, FamilyPivot.Circle, number: 4) }; concealedTiles = concealedTiles.OrderBy(t => GlobalTools.Randomizer.NextDouble()).ToList(); List <List <TileComboPivot> > result = HandPivot.IsCompleteBasic(concealedTiles, new List <TileComboPivot>()); Assert.IsNotNull(result); Assert.AreEqual(1, result.Count); AssertFiveCombinationsIncludingOnePair(result[0]); }
// Common trunk of the kan call process. private void CommonCallKan(int?previousPlayerIndex, TilePivot compensationTile) { Dispatcher.Invoke(() => { if (previousPlayerIndex.HasValue) { FillDiscardPanel(previousPlayerIndex.Value); } FillCombinationStack(_game.Round.CurrentPlayerIndex); SetActionButtonsVisibility(cpuPlay: !_game.Round.IsHumanPlayer, preDiscard: _game.Round.IsHumanPlayer); StpDoras.SetDorasPanel(_game.Round.DoraIndicatorTiles, _game.Round.VisibleDorasCount); }); }
// Pick action (human or CPU). private void Pick() { TilePivot pick = _game.Round.Pick(); Dispatcher.Invoke(() => { SetPlayersLed(); if (_game.Round.IsHumanPlayer) { SetActionButtonsVisibility(preDiscard: true); } }); }
private bool ExtractFromForm(out List <TilePivot> tiles, out TilePivot latestPickTile, out WindPivot dominantWind, out WindPivot seatWind) { tiles = new List <TilePivot>(); latestPickTile = null; dominantWind = WindPivot.East; seatWind = WindPivot.East; for (int i = 1; i <= 14; i++) { if (GetCombo(i).SelectedItem == null) { MessageBox.Show("Some slots are empty.", "LionelRiichiStats - Error"); return(false); } TilePivot tile = GetCombo(i).SelectedItem as TilePivot; if (GetRadio(i).IsChecked == true && latestPickTile == null) { latestPickTile = tile; } else { tiles.Add(tile); } } if (latestPickTile == null) { MessageBox.Show("The latest tile has not been selected.", "LionelRiichiStats - Error"); return(false); } if (tiles.Concat(new List <TilePivot> { latestPickTile }).GroupBy(t => t).Any(tg => tg.Count() > 4)) { MessageBox.Show("One tile is selected more than four times.", "LionelRiichiStats - Error"); return(false); } if (CbbDominantWind.SelectedIndex == -1) { MessageBox.Show("The dominant wind has not been selected.", "LionelRiichiStats - Error"); return(false); } if (CbbSeatWind.SelectedIndex == -1) { MessageBox.Show("The seat wind has not been selected.", "LionelRiichiStats - Error"); return(false); } dominantWind = (WindPivot)CbbDominantWind.SelectedItem; seatWind = (WindPivot)CbbSeatWind.SelectedItem; return(true); }
// Inner process kan call. private void HumanKanCallProcess(TilePivot tile, int?previousPlayerIndex) { TilePivot compensationTile = _game.Round.CallKan(GamePivot.HUMAN_INDEX, tile); InvokeOverlay("Kan", GamePivot.HUMAN_INDEX); if (CheckOpponensRonCall(false)) { _game.Round.UndoPickCompensationTile(); NewRound(_game.Round.CurrentPlayerIndex); } else { CommonCallKan(previousPlayerIndex, compensationTile); if (_game.Round.CanCallTsumo(true)) { BtnTsumo.Visibility = Visibility.Visible; BtnSkipCall.Visibility = Visibility.Visible; if (Properties.Settings.Default.AutoCallMahjong) { BtnTsumo.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); } else { ActivateTimer(null); } } else { _riichiTiles = _game.Round.CanCallRiichi(); if (_riichiTiles.Count > 0) { BtnRiichi.Visibility = Visibility.Visible; BtnSkipCall.Visibility = Visibility.Visible; ActivateTimer(null); } else if (Properties.Settings.Default.AutoDiscardAfterRiichi && _game.Round.HumanCanAutoDiscard()) { // Auto discard if riichi and the compensation tile is not interesting // Never tested! RaiseButtonClickEvent(new PanelButton("StpPickP", 0)); } } } }
// Proceeds to call a kan for an opponent. private TilePivot OpponentBeginCallKan(int playerId, TilePivot kanTilePick, bool concealedKan, bool fromPreviousKan) { TilePivot compensationTile = _game.Round.CallKan(playerId, concealedKan ? kanTilePick : null); if (compensationTile != null) { InvokeOverlay("Kan", playerId); Thread.Sleep(((CpuSpeedPivot)Properties.Settings.Default.CpuSpeed).ParseSpeed()); Dispatcher.Invoke(() => { if (!concealedKan) { SetPlayersLed(); } }); } return(compensationTile); }
// Discard action (human or CPU). private void Discard(TilePivot tile) { if (_game.Round.Discard(tile)) { if (!_game.Round.PreviousIsHumanPlayer) { Thread.Sleep(((CpuSpeedPivot)Properties.Settings.Default.CpuSpeed).ParseSpeed()); } Dispatcher.Invoke(() => { FillHandPanel(_game.Round.PreviousPlayerIndex); FillDiscardPanel(_game.Round.PreviousPlayerIndex); SetActionButtonsVisibility(cpuPlay: !_game.Round.PreviousIsHumanPlayer); }); if (_game.Round.PreviousIsHumanPlayer) { RunAutoPlay(); } } }
// Initializes a background worker which orchestrates the CPU actions. private void InitializeAutoPlayWorker() { _autoPlay = new BackgroundWorker { WorkerReportsProgress = false, WorkerSupportsCancellation = false }; _autoPlay.DoWork += delegate(object sender, DoWorkEventArgs evt) { object[] argumentsList = evt.Argument as object[]; bool skipCurrentAction = (bool)argumentsList[0]; bool humanRonPending = (bool)argumentsList[1]; Tuple <int, TilePivot, int?> kanInProgress = null; AutoPlayResult result = new AutoPlayResult { EndOfRound = false, PanelButton = null, RonPlayerId = null }; while (true && !_hardStopAutoplay) { if (!skipCurrentAction && !humanRonPending && _game.Round.CanCallRon(GamePivot.HUMAN_INDEX)) { Dispatcher.Invoke(() => { BtnRon.Visibility = Visibility.Visible; BtnSkipCall.Visibility = Visibility.Visible; }); ActivateTimer(null); if (Properties.Settings.Default.AutoCallMahjong) { result.PanelButton = new PanelButton("BtnRon", -1); } break; } if (CheckOpponensRonCall(humanRonPending)) { result.EndOfRound = true; result.RonPlayerId = kanInProgress != null ? kanInProgress.Item1 : _game.Round.PreviousPlayerIndex; if (kanInProgress != null) { _game.Round.UndoPickCompensationTile(); } break; } if (kanInProgress != null) { CommonCallKan(kanInProgress.Item3, kanInProgress.Item2); } if (!skipCurrentAction && _game.Round.CanCallPonOrKan(GamePivot.HUMAN_INDEX)) { break; } Tuple <int, TilePivot> opponentWithKanTilePick = _game.Round.IaManager.KanDecision(false); if (opponentWithKanTilePick != null) { int previousPlayerIndex = _game.Round.PreviousPlayerIndex; TilePivot compensationTile = OpponentBeginCallKan(opponentWithKanTilePick.Item1, opponentWithKanTilePick.Item2, false, false); kanInProgress = new Tuple <int, TilePivot, int?>(opponentWithKanTilePick.Item1, compensationTile, previousPlayerIndex); continue; } int opponentPlayerId = _game.Round.IaManager.PonDecision(); if (opponentPlayerId > -1) { PonCall(opponentPlayerId); continue; } if (!skipCurrentAction && _game.Round.IsHumanPlayer && _game.Round.CanCallChii().Count > 0) { break; } Tuple <TilePivot, bool> chiiTilePick = _game.Round.IaManager.ChiiDecision(); if (chiiTilePick != null) { ChiiCall(chiiTilePick); continue; } if (kanInProgress != null) { if (OpponentAfterPick(ref kanInProgress)) { break; } continue; } if (_game.Round.IsWallExhaustion) { result.EndOfRound = true; break; } if (_game.Round.IsHumanPlayer) { result.PanelButton = HumanAutoPlay(); break; } else { Pick(); if (OpponentAfterPick(ref kanInProgress)) { result.EndOfRound = true; break; } } } evt.Result = result; }; _autoPlay.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs evt) { if (!_hardStopAutoplay) { AutoPlayResult autoPlayResult = evt.Result as AutoPlayResult; if (autoPlayResult.EndOfRound) { NewRound(autoPlayResult.RonPlayerId); } else { RaiseButtonClickEvent(autoPlayResult.PanelButton); } } }; }