## 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, math

# 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 pysoltk import MfxCanvasText, getFont


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

class Braid_Hint(DefaultHint):
    # FIXME: demo is not too clever in this game
    pass


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

class Braid_Foundation(AbstractFoundationStack):
    def __init__(self, x, y, game, suit, **cap):
        kwdefault(cap, mod=13, dir=0, base_rank=NO_RANK, 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
        if not self.cards:
            return 1
        stack_dir = self.game.getFoundationDir()
        if stack_dir == 0:
            card_dir = self.getRankDir(cards=(self.cards[-1], cards[0]))
            return card_dir in (1, -1)
        else:
            return (self.cards[-1].rank + stack_dir) % self.cap.mod == cards[0].rank


class Braid_BraidStack(OpenStack):
    def __init__(self, x, y, game, sine=0):
        OpenStack.__init__(self, x, y, game)
        self.CARD_YOFFSET = self.game.app.images.CARD_YOFFSET
        CW = self.game.app.images.CARDW
        if sine:
            # use a sine wave for the x offsets
            self.CARD_XOFFSET = []
            n = 9
            dx = 0.4 * CW * (2*math.pi/n)
            last_x = 0
            for i in range(n):
                x = int(round(dx * math.sin(i + 1)))
                ##print x, x - last_x
                self.CARD_XOFFSET.append(x - last_x)
                last_x = x
        else:
            self.CARD_XOFFSET = (-0.45*CW, 0.35*CW, 0.55*CW, -0.45*CW)


class Braid_RowStack(ReserveStack):
    def fillStack(self):
        if not self.cards and self.game.s.braid.cards:
            self.game.moveMove(1, self.game.s.braid, self)

    def getBottomImage(self):
        return self.game.app.images.getBraidBottom()


class Braid_ReserveStack(ReserveStack):
    def acceptsCards(self, from_stack, cards):
        if from_stack is self.game.s.braid or from_stack in self.game.s.rows:
            return 0
        return ReserveStack.acceptsCards(self, from_stack, cards)

    def getBottomImage(self):
        return self.game.app.images.getTalonBottom()


# /***********************************************************************
# // Braid
# ************************************************************************/

class Braid(Game):
    Hint_Class = Braid_Hint

    BRAID_CARDS = 20
    RANKS = RANKS           # pull into class Braid

    #
    # game layout
    #

    def createGame(self):
        # create layout
        l, s = Layout(self), self.s

        # set window
        # (piles up to 20 cards are playable - needed for Braid_BraidStack)
        h = max(4*l.YS + 30, l.YS+(self.BRAID_CARDS-1)*l.YOFFSET)
        self.setSize(10*l.XS+l.XM, l.YM + h)

        # extra settings
        self.base_card = None

        # create stacks
        s.addattr(braid=None)      # register extra stack variable
        x, y = l.XM, l.YM
        for i in range(2):
            s.rows.append(Braid_RowStack(x + 0.5*l.XS, y, self))
            s.rows.append(Braid_RowStack(x + 4.5*l.XS, y, self))
            y = y + 3 * l.YS
        y = l.YM + l.YS
        for i in range(2):
            s.rows.append(Braid_ReserveStack(x, y, self))
            s.rows.append(Braid_ReserveStack(x + l.XS, y, self))
            s.rows.append(Braid_ReserveStack(x, y + l.YS, self))
            s.rows.append(Braid_ReserveStack(x + l.XS, y + l.YS, self))
            x = x + 4 * l.XS
        x, y = l.XM + l.XS * 5/2, l.YM
        s.braid = Braid_BraidStack(x, y, self)
        x, y = l.XM + 7 * l.XS, l.YM + l.YS * 3/2
        s.talon = WasteTalonStack(x, y, self, max_rounds=3)
        l.createText(s.talon, "ss")
        s.talon.texts.rounds = MfxCanvasText(self.canvas,
                               x + l.CW / 2, y - l.YM,
                               anchor="s")
        x = x - l.XS
        s.waste = WasteStack(x, y, self)
        l.createText(s.waste, "ss")
        x = l.XM + 8 * l.XS
        y = l.YM
        for i in range(4):
            s.foundations.append(Braid_Foundation(x, y, self, i))
            s.foundations.append(Braid_Foundation(x + l.XS, y, self, i))
            y = y + l.YS
        self.texts.info = MfxCanvasText(self.canvas,
                               x + l.CW + l.XM / 2, y,
                               anchor="n", font=getFont("canvas_card", cardw=l.CW))

        # define stack-groups
        self.sg.talonstacks = [s.talon] + [s.waste]
        self.sg.openstacks = s.foundations + s.rows
        self.sg.dropstacks = [s.braid] + s.rows + [s.waste]


    #
    # game overrides
    #

    def _shuffleHook(self, cards):
        # do not play a trump as the base_card
        n = m = -1 - self.BRAID_CARDS - len(self.s.rows)
        while cards[n].suit >= len(self.gameinfo.suits):
            n = n - 1
        cards[n], cards[m] = cards[m], cards[n]
        return cards

    def startGame(self):
        self.base_card = None
        self.updateText()
        self.startDealSample()
        for i in range(self.BRAID_CARDS):
            self.s.talon.dealRow(rows=[self.s.braid], frames=4)
        self.s.talon.dealRow(frames=4)
        # deal base_card to foundations
        self.base_card = self.s.talon.cards[-1]
        to_stack = self.s.foundations[2 * self.base_card.suit]
        self.flipMove(self.s.talon)
        self.moveMove(1, self.s.talon, to_stack)
        self.updateText()
        for s in self.s.foundations:
            s.cap.base_rank = self.base_card.rank
        # deal first card to WasteStack
        self.s.talon.dealCards()

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

    def getHighlightPilesStacks(self):
        return ()

    def _restoreGameHook(self, game):
        self.base_card = self.cards[game.loadinfo.base_card_id]
        for s in self.s.foundations:
            s.cap.base_rank = self.base_card.rank

    def _loadGameHook(self, p):
        self.loadinfo.addattr(base_card_id=None)    # register extra load var.
        self.loadinfo.base_card_id = p.load()

    def _saveGameHook(self, p):
        p.dump(self.base_card.id)


    #
    # game extras
    #

    def updateText(self):
        if self.preview > 1 or not self.texts.info:
            return
        if not self.base_card:
            t = ""
        else:
            t = self.RANKS[self.base_card.rank]
            dir = self.getFoundationDir()
            if dir == 1:
                t = t + " Ascending"
            elif dir == -1:
                t = t + " Descending"
        self.texts.info.config(text=t)


class LongBraid(Braid):
    BRAID_CARDS = 24


# register the game
registerGame(GameInfo(12, Braid, "Braid",
                      GI.GT_NAPOLEON, 2, 2))
registerGame(GameInfo(175, LongBraid, "Long Braid",
                      GI.GT_NAPOLEON, 2, 2))


