## vim:ts=4:et:nowrap
##
##---------------------------------------------------------------------------##
##
## PySol -- a Python Solitaire game
##
## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer
##
## This program 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 2 of the License, or
## (at your option) any later version.
##
## This program 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 this program; see the file COPYING.
## If not, write to the Free Software Foundation, Inc.,
## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
## Markus F.X.J. Oberhumer
## <markus@oberhumer.com>
## http://www.oberhumer.com/pysol
##
##---------------------------------------------------------------------------##


# imports
import sys

# PySol imports
if sys.modules.has_key("pysoltk"):
    from gamedb import registerGame, GameInfo, GI
    from util import *
    from stack import *
    from game import Game
    from layout import Layout
    from hint import AbstractHint, DefaultHint, CautiousDefaultHint
    from hint import SpiderType_Hint


# /***********************************************************************
# //
# ************************************************************************/

class Spider_Hint(SpiderType_Hint):
    # FIXME: demo is not too clever in this game

    BONUS_SAME_SUIT_MOVE = 400

    def _preferHighRankMoves(self):
        return 1

    def shallMovePile(self, r, t, pile, rpile):
        if not SpiderType_Hint.shallMovePile(self, r, t, pile, rpile):
            return 0
        rr = self.ClonedStack(r, stackcards=rpile)
        if rr.acceptsCards(t, pile):
            # the pile we are going to move from r to t
            # could be moved back from t ro r - this is
            # dangerous for as we can create loops...
            if len(t.cards) == 0:
                return 1
            if pile[0].suit == t.cards[-1].suit:
                # The pile will get moved onto the correct suit
                if len(rpile) == 0 or pile[0].suit != rpile[-1].suit:
                    return 1
            if self.level <= 1 and len(rpile) == 0:
                return 1
            return 0
        return 1


# /***********************************************************************
# //
# ************************************************************************/

class Spider_SS_Foundation(AbstractFoundationStack):
    def __init__(self, x, y, game, suit=ANY_SUIT, **cap):
        kwdefault(cap, dir=-1, base_rank=KING,
                  min_accept=13, max_accept=13, max_move=0)
        apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap)

    def acceptsCards(self, from_stack, cards):
        if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
            return 0
        # now check the cards
        return isSameSuitSequence(cards, self.cap.mod, self.cap.dir)


class Spider_AC_Foundation(Spider_SS_Foundation):
    def acceptsCards(self, from_stack, cards):
        if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
            return 0
        # now check the cards
        return isAlternateColorSequence(cards, self.cap.mod, self.cap.dir)


class Spider_RowStack(Spider_SS_RowStack):
    def canDropCards(self, stacks):
        if len(self.cards) < 13:
            return (None, 0)
        cards = self.cards[-13:]
        for s in stacks:
            if s is not self and s.acceptsCards(self, cards):
                return (s, 13)
        return (None, 0)


# /***********************************************************************
# // Relaxed Spider
# ************************************************************************/

class RelaxedSpider(Game):
    Layout_Method = Layout.klondikeLayout
    Talon_Class = DealRowTalonStack
    Foundation_Class = Spider_SS_Foundation
    RowStack_Class = Spider_RowStack
    Hint_Class = Spider_Hint

    def createGame(self, **layout):
        # create layout
        l, s = Layout(self), self.s
        kwdefault(layout, rows=10, waste=0, texts=1, playcards=23)
        apply(self.Layout_Method, (l,), layout)
        self.setSize(l.size[0], l.size[1])
        # create stacks
        s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self)
        if l.s.waste:
            s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self)
        for r in l.s.foundations:
            s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=ANY_SUIT))
        for r in l.s.rows:
            s.rows.append(self.RowStack_Class(r.x, r.y, self))
        # default
        l.defaultAll()

    def startGame(self):
        for i in range(4):
            self.s.talon.dealRow(flip=0, frames=0)
        self.startDealSample()
        r = self.s.rows
        rows = (r[0], r[3], r[6], r[9])
        self.s.talon.dealRow(rows=rows, flip=0)
        self.s.talon.dealRow()

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        return ((card1.rank + 1) % stack1.cap.mod == card2.rank or
                (card2.rank + 1) % stack1.cap.mod == card1.rank)


# /***********************************************************************
# // Spider
# ************************************************************************/

class Spider(RelaxedSpider):
    def canDealCards(self):
        if not RelaxedSpider.canDealCards(self):
            return 0
        # no row may be empty
        for r in self.s.rows:
            if not r.cards:
                return 0
        return 1


# /***********************************************************************
# // Black Widow
# ************************************************************************/

class BlackWidow_RowStack(RK_RowStack, Spider_RowStack):
    def canDropCards(self, stacks):
        return Spider_RowStack.canDropCards(self, stacks)


class BlackWidow(Spider):
    RowStack_Class = BlackWidow_RowStack


# /***********************************************************************
# // Scheidungsgrund (aka Ground for a Divorce)
# ************************************************************************/

class GroundForADivorce_Talon(TalonStack):
    # A single click deals a new cards to each non-empty row.
    def dealCards(self, sound=1):
        if self.cards:
            rows = filter(lambda r: r.cards, self.game.s.rows)
            if not rows:
                # deal one card to first row if all rows are emtpy
                rows = self.game.s.rows[:1]
            return self.dealRowAvail(rows=rows, sound=sound)
        return 0


class GroundForADivorce(RelaxedSpider):
    Layout_Method = Layout.harpLayout
    Talon_Class = GroundForADivorce_Talon
    Foundation_Class = StackWrapper(Spider_SS_Foundation, base_rank=ANY_RANK, mod=13)
    RowStack_Class = StackWrapper(Spider_RowStack, mod=13)

    def createGame(self):
        RelaxedSpider.createGame(self, playcards=22)

    def startGame(self):
        for i in range(4):
            self.s.talon.dealRow(frames=0)
        self.startDealSample()
        self.s.talon.dealRow()


# /***********************************************************************
# // Grandmother's Game
# ************************************************************************/

class GrandmothersGame(RelaxedSpider):
    Layout_Method = Layout.harpLayout

    def createGame(self):
        RelaxedSpider.createGame(self, playcards=22)

    def startGame(self):
        for i in range(5):
            self.s.talon.dealRow(frames=0)
        self.startDealSample()
        self.s.talon.dealRow()


# /***********************************************************************
# // Spiderette (Spider with one deck and 7 rows)
# ************************************************************************/

class Spiderette(Spider):
    def createGame(self):
        Spider.createGame(self, rows=7, playcards=20)

    def startGame(self):
        for i in range(1, len(self.s.rows)):
            self.s.talon.dealRow(rows=self.s.rows[i:], flip=0, frames=0)
        self.startDealSample()
        self.s.talon.dealRow()


# /***********************************************************************
# // Baby Spiderette
# ************************************************************************/

class BabySpiderette(Spiderette):
    RowStack_Class = BlackWidow_RowStack


# /***********************************************************************
# // Will o' the Wisp (just like Spiderette)
# ************************************************************************/

class WillOTheWisp(Spiderette):
    def startGame(self):
        for i in range(2):
            self.s.talon.dealRow(flip=0, frames=0)
        self.startDealSample()
        self.s.talon.dealRow()


# /***********************************************************************
# // Simple Simon
# ************************************************************************/

class SimpleSimon(Spider):
    Talon_Class = InitialDealTalonStack

    def createGame(self):
        Spider.createGame(self, rows=10, texts=0)

    def startGame(self):
        for l in (9, 8, 7, 6, 5, 4, 3):
            self.s.talon.dealRow(rows=self.s.rows[:l], frames=0)
        self.startDealSample()
        self.s.talon.dealRow()


# /***********************************************************************
# // Rachel
# ************************************************************************/

class Rachel(RelaxedSpider):
    Talon_Class = StackWrapper(WasteTalonStack, max_rounds=1)
    RowStack_Class = RK_RowStack

    def createGame(self):
        RelaxedSpider.createGame(self, waste=1, rows=6, texts=1)

    def startGame(self):
        self.startDealSample()
        self.s.talon.dealRow()
        self.s.talon.dealCards()          # deal first card to WasteStack


# /***********************************************************************
# // Scorpion - move cards like in Russian Solitaire
# ************************************************************************/

class Scorpion_RowStack(Yukon_SS_RowStack, Spider_RowStack):
    canDropCards = Spider_RowStack.canDropCards


class Scorpion(RelaxedSpider):
    RowStack_Class = StackWrapper(Scorpion_RowStack, base_rank=KING)

    def createGame(self):
        RelaxedSpider.createGame(self, rows=7, playcards=20)

    def startGame(self):
        for i in (4, 4, 4, 0, 0, 0):
            self.s.talon.dealRow(rows=self.s.rows[:i], flip=0, frames=0)
            self.s.talon.dealRow(rows=self.s.rows[i:], flip=1, frames=0)
        self.startDealSample()
        self.s.talon.dealRow()

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        return (card1.suit == card2.suit and
                (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank))

    def getHighlightPilesStacks(self):
        return ()


# /***********************************************************************
# // Wasp
# ************************************************************************/

class Wasp(Scorpion):
    RowStack_Class = Scorpion_RowStack      # anything on an empty space

    def startGame(self):
        for i in (3, 3, 3, 0, 0, 0):
            self.s.talon.dealRow(rows=self.s.rows[:i], flip=0, frames=0)
            self.s.talon.dealRow(rows=self.s.rows[i:], flip=1, frames=0)
        self.startDealSample()
        self.s.talon.dealRow()


# /***********************************************************************
# // Rouge et Noir
# ************************************************************************/

class RougeEtNoir_RowStack(KingAC_RowStack):
    def canDropCards(self, stacks):
        if not self.cards:
            return (None, 0)
        for s in stacks:
            for cards in (self.cards[-1:], self.cards[-13:]):
                if s is not self and s.acceptsCards(self, cards):
                    return (s, len(cards))
        return (None, 0)


class RougeEtNoir(Game):
    Layout_Method = Layout.klondikeLayout
    Talon_Class = DealRowTalonStack
    RowStack_Class = RougeEtNoir_RowStack

    def createGame(self, **layout):
        # create layout
        l, s = Layout(self), self.s
        kwdefault(layout, rows=10, waste=0, texts=1, playcards=23)
        apply(self.Layout_Method, (l,), layout)
        self.setSize(l.size[0], l.size[1])
        # create stacks
        s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self)
        if l.s.waste:
            s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self)
        for i in range(4):
            r = l.s.foundations[i]
            s.foundations.append(AC_FoundationStack(r.x, r.y, self, suit=i, max_move=0))
        for i in range(4):
            r = l.s.foundations[i+4]
            s.foundations.append(Spider_AC_Foundation(r.x, r.y, self))
        for r in l.s.rows:
            s.rows.append(self.RowStack_Class(r.x, r.y, self))
        # default
        l.defaultAll()
        return l

    def startGame(self, flip=0, reverse=1):
        for i in range(3, len(self.s.rows)):
            self.s.talon.dealRow(rows=self.s.rows[:-i], flip=flip, frames=0, reverse=reverse)
        self.startDealSample()
        self.s.talon.dealRow(rows=self.s.rows[:-1], reverse=reverse)


# register the game
registerGame(GameInfo(10, RelaxedSpider, "Relaxed Spider",
                      GI.GT_SPIDER | GI.GT_RELAXED, 2, 0))
registerGame(GameInfo(11, Spider, "Spider",
                      GI.GT_SPIDER, 2, 0))
registerGame(GameInfo(49, BlackWidow, "Black Widow",
                      GI.GT_SPIDER, 2, 0))
registerGame(GameInfo(14, GroundForADivorce, "Ground for a Divorce",
                      GI.GT_SPIDER, 2, 0))
registerGame(GameInfo(114, GrandmothersGame, "Grandmother's Game",
                      GI.GT_SPIDER, 2, 0))
registerGame(GameInfo(24, Spiderette, "Spiderette",
                      GI.GT_SPIDER, 1, 0))
registerGame(GameInfo(47, BabySpiderette, "Baby Spiderette",
                      GI.GT_SPIDER, 1, 0))
registerGame(GameInfo(48, WillOTheWisp, "Will o' the Wisp",
                      GI.GT_SPIDER, 1, 0))
registerGame(GameInfo(50, SimpleSimon, "Simple Simon",
                      GI.GT_SPIDER, 1, 0))
##registerGame(GameInfo(194, Rachel, "Rachel",
##                      GI.GT_SPIDER | GI.GT_XORIGINAL, 1, 0))
registerGame(GameInfo(29, Scorpion, "Scorpion",
                      GI.GT_SPIDER, 1, 0))
registerGame(GameInfo(185, Wasp, "Wasp",
                      GI.GT_SPIDER, 1, 0))
registerGame(GameInfo(220, RougeEtNoir, "Rouge et Noir",
                      GI.GT_GYPSY, 2, 0))

