Пример #1
0
        public void SomeHandByTsumo(string handString, int roundWind, int seatWind, string discardString, Yaku expectedYaku)
        {
            var discard = TileType.FromString(discardString);
            var sp      = new ShorthandParser(handString);
            var hand    = new HandCalculator(sp);
            var wind    = new WindScoringData(roundWind, seatWind);

            var(yaku, fu) = ScoreCalculator.TsumoWithYaku(hand.ScoringData, wind, discard);

            Assert.Equal(expectedYaku, yaku);
        }
Пример #2
0
        public void TotalFuTsumo(string handString, int roundWind, int seatWind, string drawString, int expectedFu)
        {
            var draw = TileType.FromString(drawString);
            var sp   = new ShorthandParser(handString);
            var hand = new HandCalculator(sp);
            var wind = new WindScoringData(roundWind, seatWind);

            var(han, fu) = ScoreCalculator.Tsumo(hand.ScoringData, wind, draw);

            Assert.Equal(expectedFu, fu);
        }
Пример #3
0
        public static bool CanRon(Board board, int seatIndex)
        {
            var seat = board.Seats[seatIndex];

            if (seat.IgnoredRonFuriten)
            {
                return(false);
            }

            var winningTile = board.Seats[board.ActiveSeatIndex].CurrentDiscard !;
            var hand        = (HandCalculator)seat.Hand.WithTile(winningTile.TileType);

            if (hand.Shanten != -1)
            {
                return(false);
            }

            var furitenTileTypes = seat.Hand.GetFuritenTileTypes().ToList();

            if (furitenTileTypes.Intersect(seat.Discards.Select(t => t.TileType)).Any())
            {
                return(false);
            }

            // Most yaku
            var w = new WindScoringData(board.RoundWind.Index, seat.SeatWind.Index);

            if (hand.ScoringData.YakuAndFu(w, winningTile.TileType, true).Item1 != 0)
            {
                return(true);
            }

            // riichi, double riichi, ippatsu
            if (seat.DeclaredRiichi)
            {
                return(true);
            }

            // chiihou
            if (board.IsFirstGoAround)
            {
                return(true);
            }

            // houtei raoyui
            if (board.Wall.RemainingDraws == 0)
            {
                return(true);
            }

            return(false);
        }
Пример #4
0
        public static bool CanTsumo(Board board, bool isRinshanDraw)
        {
            var seat = board.ActiveSeat;

            if (seat.Hand.Shanten != -1)
            {
                return(false);
            }

            // Most yaku
            var w = new WindScoringData(board.RoundWind.Index, seat.SeatWind.Index);

            if (seat.Hand.ScoringData.YakuAndFu(w, seat.CurrentDraw !.TileType, false).Item1 != 0)
            {
                return(true);
            }

            // riichi, double riichi, ippatsu
            if (seat.DeclaredRiichi)
            {
                return(true);
            }

            // tenhou
            if (board.IsFirstGoAround)
            {
                return(true);
            }

            // haitei raoyue
            if (board.Wall.RemainingDraws == 0)
            {
                return(true);
            }

            // rinshan kaihou
            if (seat.CurrentDraw != null && isRinshanDraw && board.Wall.RemainingDraws > 0)
            {
                return(true);
            }

            return(false);
        }
Пример #5
0
        public (long, int) YakuAndFu(WindScoringData windScoringData, TileType winningTile, bool isRon)
        {
            Span <long> waitShiftValues = stackalloc long[4];

            _waitShiftValues.CopyTo(waitShiftValues);

            var winningTileIndex = winningTile.Index;
            var winningTileSuit  = winningTile.SuitId;

            waitShiftValues[winningTileSuit] >>= winningTileIndex + 1;

            var suitsAnd      = _suitOr[0] & _suitOr[1] & _suitOr[2];
            var sanshokuShift = (int)suitsAnd & 0b11111;
            var sanshoku      = (suitsAnd >> sanshokuShift) & SanshokuYakuFilter;

            /*
             * Pinfu
             * Honors are shifted by an amount based on value winds to make sure only guest wind pairs are possible
             * The suit with the winning tile is shifted by the drawn tile to ensure ryanmen wait and non-honor wait (also used for other yaku)
             * After that, some constellations where pinfu is not possible because of other yaku locking shapes are eliminated:
             * Sanankou and sanshoku. Ittsuu is a single suit issue and has been dealt with in the lookup preparation.
             * Some chiitoitsu hands evaluate to pinfu by the previous steps, despite being clearly not pinfu.
             * A flag in BigSum is created by adding 1 for each suit with a pair and a 2 for honors.
             * This will leave a 0 in the second bit in the bad case: 11223399m11p11s44z This 0 is aligned with the pinfu bit index.
             */
            var bigSum = (_suitOr[0] & SuitBigSumFilter) +
                         (_suitOr[1] & SuitBigSumFilter) +
                         (_suitOr[2] & SuitBigSumFilter) +
                         (_honorOr & HonorBigSumFilter);
            var honorWindShift   = _honorOr >> windScoringData.HonorShift;
            var waitAndWindShift = waitShiftValues[0] & waitShiftValues[1] & waitShiftValues[2] & waitShiftValues[3] & honorWindShift;
            var pinfu            = waitAndWindShift &
                                   // TODO suitsAnd + 1 should instead be handled in bitField preparation
                                   (_suitOr[winningTileSuit] >> (int)((winningTileIndex + ((suitsAnd + 1) & 1)) * (sanshoku >> 6))) &
                                   bigSum &
                                   PinfuYakuFilter;

            var tankiBit = waitShiftValues[winningTileSuit] & 0b1L;

            // get this before ron shifting
            // TODO get rid of this conditional / check if this is converted to (0/1) - 1 AND difference
            var singleWaitFuJihai = winningTileSuit == 3 ? (waitShiftValues[winningTileSuit] >> 9) & 2L : 0;

            // TODO waitAndRonShift is only used for ankou now, so maybe shifting inside the array is not necessary anymore?
            // TODO might be able to rework ron shift to not use up so many bits.
            // TODO check if this is converted to (0/1) - 1 AND difference
            var ronShiftAmount = isRon ? 9 : 0;

            waitShiftValues[winningTileSuit] >>= ronShiftAmount;

            var waitAndRonShift = (waitShiftValues[0] & RonShiftSumFilter) +
                                  (waitShiftValues[1] & RonShiftSumFilter) +
                                  (waitShiftValues[2] & RonShiftSumFilter) +
                                  (waitShiftValues[3] & RonShiftSumFilter);

            waitAndRonShift += _shiftedAnkanCount;

            waitAndRonShift += bigSum & (0b111L << AnkouRonShiftSumFilterIndex);
            waitAndRonShift += waitAndRonShift & (0b101L << AnkouRonShiftSumFilterIndex);

            var bigAnd = suitsAnd & _honorOr;

            var result = 0L;

            result |= ((1L << BitIndex.MenzenTsumo) >> ronShiftAmount) & (1L << BitIndex.MenzenTsumo);
            result |= waitAndRonShift & AnkouYakuFilter;
            result |= sanshoku;
            result |= pinfu;
            result |= bigAnd & BigAndYakuFilter;

            bigSum |= bigAnd & _bigAndToSumFilter;

            var bigSumPostElimination = bigSum & ~((bigSum & BigSumEliminationFilter) >> EliminationDelta);

            result |= bigSumPostElimination & BigSumPostEliminationYakuFilter;

            result |= _honorSum & HonorSumYakuFilter & windScoringData.ValueWindFilter;

            result |= _sankantsuSuukantsu & (11L << BitIndex.Sankantsu);

            var ryuuiisouSum = (_suitOr[0] & RyuuiisouSumFilter01) +
                               (_suitOr[1] & RyuuiisouSumFilter01) +
                               (_suitOr[2] & RyuuiisouSumFilter2) +
                               _honorSum;

            result |= ryuuiisouSum & (1L << BitIndex.Ryuuiisou);

            if ((result & (1L << BitIndex.Chiitoitsu)) != 0)
            {
                result &= ~((1L << BitIndex.ClosedChanta) | (1L << BitIndex.Iipeikou));
            }

            var openIipeikouBit = (result >> BitIndex.Iipeikou) & 1L;
            var sanankouBit     = (result >> BitIndex.Sanankou) & 1L;

            // get this before shifting to open ssk
            var sanshokuFuMultiplier = (int)(((result >> BitIndex.SanshokuDoukou) + (result >> BitIndex.ClosedSanshokuDoujun)) & 1);

            result += (result & OpenBitFilter) * _openBit;

            var closedChantaBit  = (result >> BitIndex.ClosedChanta) & 1L;
            var closedJunchanBit = (result >> BitIndex.ClosedJunchan) & 1L;
            var openJunchanBit   = (result >> BitIndex.OpenJunchan) & 1L;
            var toitoiBit        = (result >> BitIndex.Toitoi) & 1L;

            var x = openIipeikouBit & (closedChantaBit | closedJunchanBit);
            var removeSequenceYaku = (sanankouBit ^ x) & sanankouBit;
            var removeOpenJunchan  = openIipeikouBit & (sanankouBit | toitoiBit) & openJunchanBit;
            var removeSanankou     = x * (1 - toitoiBit);

            result -= (result & (1L << BitIndex.Sanankou)) * removeSanankou;
            // (openIipeikouBit << BitIndex.OpenChanta) means 111222333 shape and chanta, here excluded in case of sanankou
            result -= (result & ((1L << BitIndex.Pinfu) | (1L << BitIndex.Iipeikou) | (openIipeikouBit << BitIndex.OpenChanta))) * removeSequenceYaku;
            result -= (result & (1L << BitIndex.OpenJunchan)) * removeOpenJunchan;
            result -= (result & ((1L << BitIndex.Iipeikou) | (1L << BitIndex.ClosedJunchan))) * (toitoiBit & (1 - _openBit));

            result += (result & TankiUpgradeableFilter) * tankiBit;

            // This covers the 22234555m222p222s case where sanankou/sanshoku doukou depend on the wait.
            var sanankouAndDoukou  = (_suitOr[winningTileSuit] >> (BitIndex.Sanankou - BitIndex.SanshokuDoukou + 1)) & (1L << BitIndex.SanshokuDoukou);
            var waitPreventsDoukou = (suitsAnd >> (winningTileIndex + ronShiftAmount - 9)) & sanankouAndDoukou;

            result -= waitPreventsDoukou & (result >> (BitIndex.Sanankou - BitIndex.SanshokuDoukou));

            result &= _baseMask & _baseMaskFilter;

            var yakuman = result & YakumanFilter;

            if (yakuman != 0)
            {
                return(yakuman, 0);
            }

            if ((result & (1L << BitIndex.Chiitoitsu)) != 0)
            {
                return(result, 25);
            }

            var closedRonFu = (int)(1 - _openBit) * (10 >> (9 - ronShiftAmount));

            if ((result & PinfuYakuFilter) != 0)
            {
                return(result, 20 + closedRonFu);
            }

            var squareTypeToShuntsu = ((ryuuiisouSum & ~result) >> BitIndex.Sanankou) & ((1L - _openBit) | (result >> BitIndex.OpenJunchan) | ((result >> BitIndex.OpenChanta) & 1L));

            var footprintKey = (sanshokuShift + 1) * 40 * sanshokuFuMultiplier;

            footprintKey += (int)squareTypeToShuntsu * 40;
            footprintKey |= ronShiftAmount & 1; // ronShiftAmount is either 0 or 0b1001
            footprintKey |= (int)_openBit << 1;

            Span <int> keys = stackalloc int[] { footprintKey, footprintKey, footprintKey, 0 };

            keys[winningTileSuit] += (winningTileIndex + 1) * 4;

            var fuM = FuFootprint(0, keys[0]);
            var fuP = FuFootprint(1, keys[1]);
            var fuS = FuFootprint(2, keys[2]);

            // TODO jihai single wait and ankou fu the same way as suits, maybe with value wind info instead of ssk, incorporating valuePairFu
            var bonusAnkouCountWinningSuit          = (waitShiftValues[winningTileSuit] >> 32) & 1L;
            var potentialBonusAnkouCountWinningSuit = (_waitShiftValues[winningTileSuit] >> 32) & 1L;
            var ankouFuCorrection = (potentialBonusAnkouCountWinningSuit - bonusAnkouCountWinningSuit) * ((0b100000 >> winningTileSuit) & 0b100);
            var ankouFuJihai      = ((_waitShiftValues[3] >> 17) & (0b111L << 3)) - ankouFuCorrection;

            var tsumoFu = 2 >> ronShiftAmount;

            // lowest bit of honorOr is 1 iff there is a wind pair
            var valueWindAdjuster = 1 + (_honorOr & windScoringData.DoubleValueWindBit);
            var valuePairFu       = (honorWindShift & 1L) << (int)valueWindAdjuster;

            var fu = _baseAndMeldFu + closedRonFu + tsumoFu + (int)valuePairFu + fuM + fuP + fuS + (int)ankouFuJihai + (int)singleWaitFuJihai;

            var r        = fu % 10;
            var rounding = r == 0 && fu != 20 ? 0 : 10 - r;

            return(result, fu + rounding);
        }