1#!/usr/bin/env python
2# -*- mode: python; coding: utf-8; -*-
3# ---------------------------------------------------------------------------##
4#
5#  Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
6#  Copyright (C) 2003 Mt. Hood Playing Card Co.
7#  Copyright (C) 2005-2009 Skomoroh
8#
9#  This program is free software: you can redistribute it and/or modify
10#  it under the terms of the GNU General Public License as published by
11#  the Free Software Foundation, either version 3 of the License, or
12#  (at your option) any later version.
13#
14#  This program is distributed in the hope that it will be useful,
15#  but WITHOUT ANY WARRANTY; without even the implied warranty of
16#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17#  GNU General Public License for more details.
18#
19#  You should have received a copy of the GNU General Public License
20#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21#
22# ---------------------------------------------------------------------------##
23
24
25# imports
26
27# PySol imports
28from pysollib.mfxutil import Struct
29from pysollib.pysoltk import MfxCanvasText
30
31
32# ************************************************************************
33# * a helper class to create common layouts
34# ************************************************************************
35
36# a layout stack
37class _LayoutStack:
38    def __init__(self, x, y, suit=None):
39        self.x = int(round(x))
40        self.y = int(round(y))
41        self.suit = suit
42        self.text_args = {}
43        self.text_format = "%d"
44
45    def setText(self, x, y, anchor="center", format=None, **kw):
46        self.text_args["x"] = x
47        self.text_args["y"] = y
48        self.text_args["anchor"] = anchor
49        self.text_args.update(kw)
50        if format is not None:
51            self.text_format = format
52
53
54class Layout:
55    def __init__(self, game, card_x_space=None, card_y_space=None, **kw):
56        self.game = game
57        self.canvas = self.game.canvas
58        self.size = None
59        self.s = Struct(
60            talon=None,
61            waste=None,
62            foundations=[],
63            rows=[],
64            reserves=[],
65        )
66        self.stackmap = {}
67        self.regions = []
68        # set visual constants
69        images = self.game.app.images
70
71        layout_x_margin = images.CARDW // 9
72        layout_y_margin = layout_x_margin
73        layout_card_x_space = images.CARDW // 9
74        layout_card_y_space = images.CARDH // 8
75
76        self.CW = images.CARDW
77        self.CH = images.CARDH
78        self.XOFFSET = images.CARD_XOFFSET
79        self.YOFFSET = images.CARD_YOFFSET
80        self.XM = layout_x_margin       # XMARGIN
81        self.YM = layout_y_margin       # YMARGIN
82
83        if card_x_space is None:
84            self.XS = self.CW + layout_card_x_space          # XSPACE
85        else:
86            self.XS = self.CW + card_x_space
87        if card_y_space is None:
88            self.YS = self.CH + layout_card_y_space          # YSPACE
89        else:
90            self.YS = self.CH + card_y_space
91
92        # self.CARD_X_SPACE = layout_card_x_space
93        # self.CARD_Y_SPACE = layout_card_y_space
94        # self.RIGHT_MARGIN = layout_x_margin-layout_card_x_space
95        # self.BOTTOM_MARGIN = layout_y_margin-layout_card_y_space
96
97        font = game.app.getFont("canvas_default")
98        # self.TEXT_MARGIN = 10
99        self.TEXT_MARGIN = font[1]
100        # self.TEXT_HEIGHT = 30
101        self.TEXT_HEIGHT = 18+font[1]
102
103        self.__dict__.update(kw)
104        if self.game.preview > 1:
105            if "XOFFSET" in kw:
106                self.XOFFSET //= self.game.preview
107            if "YOFFSET" in kw:
108                self.YOFFSET //= self.game.preview
109            self.TEXT_HEIGHT = 10
110
111    def __createStack(self, x, y, suit=None):
112        stack = _LayoutStack(x, y, suit)
113        mapkey = (stack.x, stack.y)
114        # from pprint import pprint
115        # print mapkey
116        # pprint(self.stackmap)
117        assert mapkey not in self.stackmap
118        self.stackmap[mapkey] = stack
119        return stack
120
121    def _setText(self, stack, anchor="center"):
122        tx, ty, ta, tf = self.getTextAttr(stack, anchor)
123        stack.setText(tx, ty, ta, tf)
124
125    #
126    #
127    #
128
129    def createGame(self, layout_method,
130                   talon_class=None,
131                   waste_class=None,
132                   foundation_class=None,
133                   row_class=None,
134                   reserve_class=None,
135                   **kw
136                   ):
137        # create layout
138        game = self.game
139        s = game.s
140        layout_method(self, **kw)
141        game.setSize(self.size[0], self.size[1])
142        # create stacks
143        if talon_class:
144            s.talon = talon_class(self.s.talon.x, self.s.talon.y, game)
145        if waste_class:
146            s.waste = waste_class(self.s.waste.x, self.s.waste.y, game)
147        if foundation_class:
148            if isinstance(foundation_class, (list, tuple)):
149                n = len(self.s.foundations)//len(foundation_class)
150                i = 0
151                for j in range(n):
152                    for cls in foundation_class:
153                        r = self.s.foundations[i]
154                        s.foundations.append(cls(r.x, r.y, game, suit=r.suit))
155                        i += 1
156
157            else:
158                for r in self.s.foundations:
159                    s.foundations.append(foundation_class(r.x, r.y, game,
160                                                          suit=r.suit))
161        if row_class:
162            for r in self.s.rows:
163                s.rows.append(row_class(r.x, r.y, game))
164        if reserve_class:
165            for r in self.s.reserves:
166                s.reserves.append(reserve_class(r.x, r.y, game))
167        # default
168        self.defaultAll()
169        # reserves texts
170        if self.s.reserves and ('reserve_texts' in kw) and kw['reserve_texts']:
171            game = self.game
172            for i in range(len(game.s.reserves)):
173                s1 = game.s.reserves[i]
174                s2 = self.s.reserves[i]
175                s1.texts.ncards = self.defaultText(s2)
176
177    #
178    # public util for use by class Game
179    #
180
181    def getTextAttr(self, stack, anchor):
182        x, y = 0, 0
183        if stack is not None:
184            x, y = stack.x, stack.y
185        delta_x, delta_y = 4, 4
186        delta_yy = 10
187        d = {
188            "n": (x+self.CW//2,       y-delta_y,          "s",  "%d"),
189            "nn": (x+self.CW//2,       y-delta_yy,         "s",  "%d"),
190            "s": (x+self.CW//2,       y+self.CH+delta_y,  "n",  "%d"),
191            "ss": (x+self.CW//2,       y+self.CH+delta_yy, "n",  "%d"),
192            "nw": (x-delta_x,         y,                  "ne", "%d"),
193            "sw": (x-delta_x,         y+self.CH,          "se", "%d"),
194            "ne": (x+self.CW+delta_x, y,                  "nw", "%d"),
195            "se": (x+self.CW+delta_x, y+self.CH,          "sw", "%d"),
196            "w": (x-delta_x,         y+self.CH//2,        "e",  "%d"),
197            "e": (x+self.CW+delta_x, y+self.CH//2,        "w",  "%d"),
198            }
199        return d[anchor]
200
201    def createText(self, stack, anchor, dx=0, dy=0, text_format=""):
202        if self.canvas.preview > 1:
203            return
204        assert stack.texts.ncards is None
205        tx, ty, ta, tf = self.getTextAttr(stack, anchor)
206        font = self.game.app.getFont("canvas_default")
207        stack.texts.ncards = MfxCanvasText(self.canvas, tx+dx, ty+dy,
208                                           anchor=ta, font=font)
209        stack.texts.ncards.text_format = text_format or tf
210
211    def createRoundText(self, stack, anchor, dx=0, dy=0):
212        if self.canvas.preview > 1:
213            return
214        assert stack.texts.rounds is None
215        delta_x, delta_y = 0, 0
216        if anchor == 'nnn':
217            anchor = 'nn'
218            delta_y = -self.TEXT_MARGIN
219        elif anchor == 'sss':
220            anchor = 'ss'
221            delta_y = self.TEXT_MARGIN
222        tx, ty, ta, tf = self.getTextAttr(stack, anchor)
223        tx += delta_x + dx
224        ty += delta_y + dy
225        font = self.game.app.getFont("canvas_default")
226        stack.texts.rounds = MfxCanvasText(self.canvas, tx, ty,
227                                           anchor=ta, font=font)
228
229    def setRegion(self, stacks, rects):
230        self.regions.append((stacks, rects))
231
232    #
233    # util for use by a Game
234    #
235
236    def defaultAll(self):
237        game = self.game
238        # create texts
239        if game.s.talon:
240            game.s.talon.texts.ncards = self.defaultText(self.s.talon)
241        if game.s.waste:
242            game.s.waste.texts.ncards = self.defaultText(self.s.waste)
243        # define stack-groups
244        self.defaultStackGroups()
245        # set regions
246        self.defaultRegions()
247
248    def defaultText(self, layout_stack):
249        if self.canvas.preview > 1:
250            return None
251        # print layout_stack, layout_stack.text_args
252        if layout_stack is None or not layout_stack.text_args:
253            return None
254        layout_stack.text_args["font"] = \
255            self.game.app.getFont("canvas_default")
256        t = MfxCanvasText(self.game.canvas, **layout_stack.text_args)
257        t.text_format = layout_stack.text_format
258        return t
259
260    # define stack-groups
261    def defaultStackGroups(self):
262        game = self.game
263        waste = []
264        if game.s.waste is not None:
265            waste = [game.s.waste]
266        game.sg.talonstacks = [game.s.talon] + waste
267        game.sg.dropstacks = game.s.rows + game.s.reserves + waste
268        game.sg.openstacks = game.s.foundations + game.s.rows + game.s.reserves
269        game.sg.reservestacks = game.s.reserves
270
271    def defaultRegions(self):
272        for region in self.regions:
273            # convert layout-stacks to corresponding game-stacks
274            stacks = []
275            for s in region[0]:
276                mapkey = (s.x, s.y)
277                id = self.game.stackmap[mapkey]
278                stacks.append(self.game.allstacks[id])
279            # print stacks, region[1]
280            self.game.setRegion(stacks, region[1])
281
282    #
283    # Baker's Dozen layout
284    #  - left: 2 rows
285    #  - right: foundations, talon
286    #
287
288    def bakersDozenLayout(self, rows, texts=0, playcards=9):
289        S = self.__createStack
290        CW, CH = self.CW, self.CH
291        XM, YM = self.XM, self.YM
292        XS, YS = self.XS, self.YS
293
294        decks = self.game.gameinfo.decks
295        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)
296        halfrows = (rows + 1) // 2
297
298        # set size so that at least 9 cards are fully playable
299        h = YS + min(2*YS, (playcards-1)*self.YOFFSET)
300        h = max(h, 5*YS//2, 3*YS//2+CH)
301        h = min(h, 3*YS)
302
303        # create rows
304        x, y = XM, YM
305        for i in range(halfrows):
306            self.s.rows.append(S(x+i*XS, y))
307        for i in range(rows-halfrows):
308            self.s.rows.append(S(x+i*XS, y+h))
309
310        # create foundations
311        x, y = XM + halfrows * XS, YM
312        self.setRegion(self.s.rows, (-999, -999, x - CW // 2, 999999))
313        for suit in range(suits):
314            for i in range(decks):
315                self.s.foundations.append(S(x+i*XS, y, suit=suit))
316            y += YS
317
318        # create talon
319        h = YM + 2*h
320        self.s.talon = S(x, h - YS)
321        if texts:
322            assert 0
323
324        # set window
325        self.size = (XM + (halfrows+decks)*XS, h)
326
327    #
328    # FreeCell layout
329    #  - top: free cells, foundations
330    #  - below: rows
331    #  - left bottom: talon, waste
332    #
333
334    def freeCellLayout(self, rows=0, reserves=0, waste=0,
335                       texts=0, reserve_texts=False, playcards=18):
336        S = self.__createStack
337        CH = self.CH
338        XM, YM = self.XM, self.YM
339        XS, YS = self.XS, self.YS
340
341        decks = self.game.gameinfo.decks
342        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)
343        toprows = suits*decks
344        if reserves:
345            toprows += reserves+1
346        maxrows = max(rows, toprows)
347
348        w = XM + maxrows*XS
349
350        # set size so that at least 2//3 of a card is visible with 18 cards
351        h = CH*2//3 + (playcards-1)*self.YOFFSET
352        h = YM + YS + max(h, 3*YS)
353        if reserves and reserve_texts:
354            h += self.TEXT_HEIGHT
355
356        # create reserves & foundations
357        x, y = (w - (toprows*XS - XM))//2, YM
358        if reserves:
359            for i in range(reserves):
360                s = S(x, y)
361                self.s.reserves.append(s)
362                if reserve_texts:
363                    self._setText(s, anchor="s")
364                x += XS
365            x += XS
366        for suit in range(suits):
367            for i in range(decks):
368                self.s.foundations.append(S(x, y, suit=suit))
369                x += XS
370
371        # create rows
372        x, y = (w - (rows*XS - XM))//2, YM + YS
373        if reserves and reserve_texts:
374            y += self.TEXT_HEIGHT
375        for i in range(rows):
376            self.s.rows.append(S(x, y))
377            x += XS
378        self.setRegion(self.s.rows, (-999, y - CH // 2, 999999, 999999))
379
380        # create talon
381        x, y = XM, h - YS
382        self.s.talon = s = S(x, y)
383        if texts:
384            if waste:
385                # place text top of stack
386                self._setText(s, anchor="n")
387            else:
388                # place text right of stack
389                self._setText(s, anchor="se")
390        if waste:
391            x += XS
392            self.s.waste = s = S(x, y)
393            if texts:
394                # place text top of stack
395                self._setText(s, anchor="n")
396
397        # set window
398        self.size = (w, h)
399
400    #
401    # Gypsy layout
402    #  - left: rows
403    #  - right: foundations, talon
404    #  - bottom: reserves
405    #
406
407    def gypsyLayout(self, rows, waste=0, reserves=0,
408                    texts=1, reserve_texts=False, round_text=False,
409                    playcards=25):
410        S = self.__createStack
411        CW, CH = self.CW, self.CH
412        XM, YM = self.XM, self.YM
413        XS, YS = self.XS, self.YS
414
415        decks = self.game.gameinfo.decks
416        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)
417
418        w = XM + max(rows+decks, reserves+2+waste)*XS
419        if reserves:
420            h = YS+(playcards-1)*self.YOFFSET+YS
421        else:
422            # set size so that at least 2//3 of a card is visible with 25 cards
423            h = CH*2//3 + (playcards-1)*self.YOFFSET
424        h = YM + max(h, (suits+1)*YS)
425        if reserves and reserve_texts:
426            h += self.TEXT_HEIGHT
427
428        # create rows
429        x, y = XM, YM
430        for i in range(rows):
431            self.s.rows.append(S(x, y))
432            x += XS
433        if reserves:
434            yy = h - YS - CH//2
435        else:
436            yy = 999999
437        self.setRegion(self.s.rows, (-999, -999, x - CW // 2, yy))
438
439        # create foundations
440        x = w - decks*XS
441        for suit in range(suits):
442            for i in range(decks):
443                self.s.foundations.append(S(x+i*XS, y, suit=suit))
444            y += YS
445
446        # create talon and waste
447        x, y = x + (decks-1)*XS, h - YS
448        if texts:
449            x -= XS//2
450        self.s.talon = s = S(x, y)
451        anchor = 's'
452        if round_text:
453            anchor = 'n'
454        if texts:
455            # place text right of stack
456            self._setText(s, anchor=anchor+"e")
457        if waste:
458            x -= XS
459            self.s.waste = s = S(x, y)
460            if texts:
461                # place text left of stack
462                self._setText(s, anchor=anchor+"w")
463        # create reserves
464        x, y = XM, h-YS
465        for i in range(reserves):
466            s = S(x, y)
467            self.s.reserves.append(s)
468            if reserve_texts:
469                self._setText(s, anchor="n")
470            x += XS
471
472        # set window
473        self.size = (w, h)
474
475    #
476    # Harp layout
477    #  - top: reserves, rows
478    #  - bottom: foundations, waste, talon
479    #
480
481    def harpLayout(self, rows, waste, reserves=0,
482                   texts=1, reserve_texts=False, playcards=19):
483        S = self.__createStack
484        CH = self.CH
485        XM, YM = self.XM, self.YM
486        XS, YS = self.XS, self.YS
487
488        decks = self.game.gameinfo.decks
489        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)
490
491        w = max(reserves*XS, rows*XS, (suits*decks+waste+1)*XS,
492                (suits*decks+1)*XS+2*XM)
493        w += XM
494
495        # set size so that at least 19 cards are fully playable
496        h = YS + (playcards-1)*self.YOFFSET
497        h = max(h, 3*YS)
498        if texts:
499            h += self.TEXT_HEIGHT
500        if reserves:
501            h += YS
502        if reserves and reserve_texts:
503            h += self.TEXT_HEIGHT
504
505        # top
506        y = YM
507        if reserves:
508            if reserve_texts:
509                y += self.TEXT_HEIGHT
510            x = (w - (reserves*XS - XM))//2
511            for i in range(reserves):
512                s = S(x, y)
513                self.s.reserves.append(s)
514                x += XS
515                if reserve_texts:
516                    self._setText(s, anchor="n")
517            y += YS
518        x = (w - (rows*XS - XM))//2
519        for i in range(rows):
520            self.s.rows.append(S(x, y))
521            x += XS
522
523        # bottom
524        x, y = XM, YM + h
525        for suit in range(suits):
526            for i in range(decks):
527                self.s.foundations.append(S(x, y, suit=suit))
528                x += XS
529        if reserves:
530            yy = YM + YS - CH//2
531            if reserve_texts:
532                yy += self.TEXT_HEIGHT
533        else:
534            yy = -999
535        self.setRegion(self.s.rows, (-999, yy, 999999, y - YS // 2))
536        if waste:
537            x = w - 2*XS
538            self.s.waste = s = S(x, y)
539            if texts:
540                # place text above stack
541                self._setText(s, 'n')
542        x = w - XS
543        self.s.talon = s = S(x, y)
544        if texts:
545            # place text above stack
546            self._setText(s, 'n')
547
548        # set window
549        self.size = (w, YM + h + YS)
550
551    #
552    # Klondike layout
553    #  - top: talon, waste, foundations
554    #  - below: rows
555    #  - bottom: reserves
556    #
557
558    def klondikeLayout(self, rows=0, waste=0, reserves=0,
559                       texts=1, reserve_texts=False, round_text=False,
560                       playcards=16, center=1, text_height=0):
561        S = self.__createStack
562        CH = self.CH
563        XM, YM = self.XM, self.YM
564        XS, YS = self.XS, self.YS
565
566        decks = self.game.gameinfo.decks
567        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)
568        foundrows = 1 + (suits > 5)
569        frows = decks * suits // foundrows
570        toprows = 1 + waste + frows
571        if round_text:
572            toprows += 1
573        maxrows = max(rows, toprows, reserves)
574
575        w = XM + maxrows * XS
576        # set size so that at least 2//3 of a card is visible with 16 cards
577        h = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
578        h = max(h, 2 * YS)
579        h += YM + YS * foundrows
580        if reserves and reserve_texts:
581            h += self.TEXT_HEIGHT
582
583        # top
584        # text_height = 0
585        x, y = XM, YM
586        self.s.talon = s = S(x, y)
587        if texts:
588            if waste or not center or maxrows - frows <= 1:
589                # place text below stack
590                self._setText(s, 's')
591                text_height = self.TEXT_HEIGHT
592            else:
593                # place text right of stack
594                self._setText(s, 'ne')
595        if waste:
596            x += XS
597            self.s.waste = s = S(x, y)
598            if texts:
599                # place text below stack
600                self._setText(s, 's')
601                text_height = self.TEXT_HEIGHT
602
603        for row in range(foundrows):
604            x = w - frows * XS
605            if center and frows + 2 * (1 + waste + 1) <= maxrows:
606                # center the foundations
607                x = XM + (maxrows - frows) * XS // 2
608            for suit in range(suits // foundrows):
609                for i in range(decks):
610                    self.s.foundations.append(
611                        S(x, y, suit=suit + (row * (suits // 2))))
612                    x += XS
613            y += YS
614
615        # below
616        x = XM
617        if rows < maxrows:
618            x += (maxrows-rows) * XS//2
619        # y += YM * (3 - foundrows)
620        y += text_height
621        for i in range(rows):
622            self.s.rows.append(S(x, y))
623            x += XS
624        if reserves:
625            yy = h - CH//2
626        else:
627            yy = 999999
628        self.setRegion(self.s.rows, (-999, y-CH//2, 999999, yy))
629
630        # bottom
631        if reserves:
632            x = (maxrows-reserves)*XS//2
633            y = h
634            h += YS
635            for i in range(reserves):
636                s = S(x, y)
637                self.s.reserves.append(s)
638                x += XS
639                if reserve_texts:
640                    self._setText(s, anchor="n")
641
642        # set window
643        self.size = (w, h)
644
645    #
646    # Yukon layout
647    #  - left: rows
648    #  - right: foundations
649    #  - left bottom: talon
650    #
651
652    def yukonLayout(self, rows, texts=0, playcards=20):
653        S = self.__createStack
654        CW, CH = self.CW, self.CH
655        XM, YM = self.XM, self.YM
656        XS, YS = self.XS, self.YS
657
658        decks = self.game.gameinfo.decks
659        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)
660
661        # set size so that at least 2//3 of a card is visible with 20 cards
662        h = CH*2//3 + (playcards-1)*self.YOFFSET
663        h = YM + max(h, suits*YS)
664
665        # create rows
666        x, y = XM, YM
667        for i in range(rows):
668            self.s.rows.append(S(x, y))
669            x += XS
670        self.setRegion(self.s.rows, (-999, -999, x - CW // 2, 999999))
671
672        # create foundations
673        for suit in range(suits):
674            for i in range(decks):
675                self.s.foundations.append(S(x+i*XS, y, suit=suit))
676            y += YS
677
678        # create talon
679        x, y = XM, h - YS
680        self.s.talon = s = S(x, y)
681        if texts:
682            # place text right of stack
683            self._setText(s, 'se')
684
685        # set window
686        self.size = (XM + (rows+decks)*XS,  h)
687
688    #
689    # Easy layout
690    #  - top: talon, waste, foundations
691    #  - bottom: rows
692    #
693
694    def easyLayout(self, rows, waste, texts=1, playcards=10, center=1):
695        S = self.__createStack
696        CH = self.CH
697        XM, YM = self.XM, self.YM
698        XS, YS = self.XS, self.YS
699
700        decks = self.game.gameinfo.decks
701        ranks = len(self.game.gameinfo.ranks)
702        frows = 4 * decks // (1 + (decks >= 3))
703        toprows = 1 + waste + frows
704        maxrows = max(rows, toprows)
705        yextra = 0
706
707        # set size so that at least 2//3 of a card is visible with 10 cards
708        h = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
709        h = max(h, 2 * YS)
710
711        # top
712        x, y = XM, YM
713        self.s.talon = s = S(x, y)
714        if texts:
715            if waste or not center or maxrows - frows <= 1:
716                # place text below stack
717                self._setText(s, 's')
718                yextra = 20
719            else:
720                # place text right of stack
721                self._setText(s, 'ne')
722        if waste:
723            x += XS
724            self.s.waste = s = S(x, y)
725            if texts:
726                # place text below stack
727                self._setText(s, 's')
728        x = XM + (maxrows - frows) * XS
729        if center and frows + 2 * (1 + waste + 1) <= maxrows:
730            # center the foundations
731            x = XM + (maxrows - frows) * XS // 2
732
733        x0, y0 = x, y
734        for i in range(decks):
735            for rank in range(ranks):
736                self.s.foundations.append(S(x0, y0, suit=rank))
737                x0 += XS
738            if i == 1 and decks > 2:
739                x0, y0 = x, y + YS
740                y = y0
741
742        # bottom
743        x, y = XM, y + YS + yextra * (decks <= 2)
744        self.setRegion(self.s.rows, (-999, y - YM // 2, 999999, 999999))
745        for i in range(rows):
746            self.s.rows.append(S(x, y))
747            x += XS
748
749        # set window
750        self.size = (XM + maxrows * XS, YM + YS + yextra + h)
751
752    #
753    # Samuri layout
754    #  - top center: rows
755    #  - left & right: foundations
756    #  - bottom center: talon
757    #
758
759    def samuriLayout(self, rows, waste, texts=1, playcards=20, center=1):
760        S = self.__createStack
761        CH = self.CH
762        XM, YM = self.XM, self.YM
763        XS, YS = self.XS, self.YS
764
765        decks = self.game.gameinfo.decks
766        toprows = 2 * decks + rows
767        yextra = 0
768
769        # set size so that at least 2//3 of a card is visible with 20 cards
770        h = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
771        h = max(h, 2 * YS)
772
773        # bottom center
774        x = (XM + (toprows * XS) // 2) - XS
775        y = h
776        self.s.talon = s = S(x, y)
777        if texts:
778            if waste or not center or toprows - rows <= 1:
779                # place text below stack
780                self._setText(s, 's')
781                yextra = 20
782            else:
783                # place text right of stack
784                self._setText(s, 'ne')
785        if waste:
786            x += XS
787            self.s.waste = s = S(x, y)
788            if texts:
789                # place text below stack
790                self._setText(s, 's')
791
792        # left & right
793        x, y = XM, YM
794        d, x0, y0 = 0, x, y
795        for suit in range(12):
796            for i in range(decks):
797                x0, y0 = x + XS * i, y + YS * d
798                self.s.foundations.append(S(x0, y0, suit=suit))
799                if i == decks - 1 and suit == 5:
800                    x0, y0 = x + XS * (toprows - decks), YM
801                    d, x, y = -1, x0, y0
802            d += 1
803
804        # top center
805        x, y = XM + XS * decks, YM
806        self.setRegion(self.s.rows, (x - XM // 2, 0, x + XS * rows, 999999))
807        for i in range(rows):
808            self.s.rows.append(S(x, y))
809            x += XS
810
811        # set window
812        self.size = (XM + toprows * XS, YM + YS + yextra + h)
813
814    #
815    # Sumo layout
816    #  - top center: rows
817    #  - left & right: foundations
818    #  - bottom center: talon
819    #
820
821    def sumoLayout(self, rows, reserves, texts=0, playcards=12, center=1):
822        S = self.__createStack
823        CH = self.CH
824        XM, YM = self.XM, self.YM
825        XS, YS = self.XS, self.YS
826
827        decks = self.game.gameinfo.decks
828        assert reserves % 2 == 0
829        toprows = 12
830        maxrows = max(rows, toprows)
831        w = XM + maxrows * XS
832
833        # set size so that at least 2//3 of a card is visible with 12 cards
834        h = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
835        h = max(h, 2 * YS)
836
837        # create foundations
838        x, y = XM, YM
839        for i in range(decks):
840            for suit in range(12):
841                self.s.foundations.append(S(x, y, suit=suit))
842                x += XS
843            x, y = XM, y + YS
844
845        # create rows
846        x, y = XM + XS * ((toprows - rows) // 2), YM + YS * decks
847        for i in range(rows):
848            self.s.rows.append(S(x, y))
849            x += XS
850        self.setRegion(
851            self.s.rows,
852            (XS + XM // 2, YS * decks + YM // 2, XS * 11 - XM // 2, 999999))
853
854        # create reserves
855        x, y = XM, YM + YS * decks
856        for i in range(reserves // 2):
857            self.s.reserves.append(S(x, y))
858            y += YS
859        x, y = w - XS, YM + YS * decks
860        for i in range(reserves // 2):
861            self.s.reserves.append(S(x, y))
862            y += YS
863
864        # create talon
865        x, y = XM, h + YM
866        self.s.talon = s = S(x, y)
867        if texts:
868            # place text right of stack
869            self._setText(s, 'se')
870
871        # set window
872        self.size = (XM + toprows * XS, YM + YS + h)
873
874    #
875    # Fun layout
876    #  - top: rows
877    #  - right: foundations
878    #  - bottom right: reserves
879    #
880
881    def funLayout(self, rows, reserves, texts=0, playcards=12, center=1):
882        S = self.__createStack
883        CH = self.CH
884        XM, YM = self.XM, self.YM
885        XS, YS = self.XS, self.YS
886
887        decks = self.game.gameinfo.decks
888        ranks = len(self.game.gameinfo.ranks)
889        assert rows % 2 == 0
890        assert reserves % decks == 0
891        toprows = decks + rows // 2
892        w = XM * 2 + toprows * XS
893
894        # set size so that at least 2//3 of a card is visible with 12 cards
895        h1 = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
896        h2 = (3 + reserves // decks) * YS
897        h = max(h1, h2)
898
899        # create foundations
900        x, y = w - XS * decks, YM
901        for i in range(decks):
902            for rank in range(ranks):
903                self.s.foundations.append(S(x, y, suit=rank))
904                y += YS
905            x, y = x + XS, YM
906
907        # create rows
908        x, y = XM, YM
909        for i in range(rows // 2):
910            self.s.rows.append(S(x, y))
911            x += XS
912        x, y = XM, (YS + h) // 2
913        for i in range(rows // 2):
914            self.s.rows.append(S(x, y))
915            x += XS
916        self.setRegion(self.s.rows, (0, 0, XS * rows // 2 + XM // 2, 999999))
917
918        # create reserves
919        x, y = w - XS * decks, YM + YS * 4
920        for i in range(decks):
921            for i in range(reserves // decks):
922                self.s.reserves.append(S(x, y))
923                y += YS
924            x, y = x + XS, YM + YS * 4
925
926        # create talon
927        x, y = XM, h
928        self.s.talon = s = S(x, y)
929        if texts:
930            # place text right of stack
931            self._setText(s, 'se')
932
933        # set window
934        self.size = (w, YM + YS + h)
935
936    #
937    # Oonsoo layout
938    #  - top: talon & rows
939    #  - left: reserves
940    #  - center right: rows
941    #
942
943    def oonsooLayout(self, rows, reserves, texts=0, playcards=12, center=1):
944        S = self.__createStack
945        CH = self.CH
946        XM, YM = self.XM, self.YM
947        XS, YS = self.XS, self.YS
948
949        decks = self.game.gameinfo.decks
950        assert rows % 2 == 0
951        toprows = decks + rows // 2
952        w = XM * 2 + toprows * (XS + XM)
953
954        # set size so that at least 2//3 of a card is visible with 12 cards
955        h = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
956        h = max(h, 2 * YS)
957
958        # create talon
959        x, y = XM, YM
960        self.s.talon = s = S(x, y)
961        if texts:
962            # place text below stack
963            self._setText(s, 's')
964
965        # create rows
966        x, y = XS + XM * 3, YM
967        for i in range(rows // 2):
968            self.s.rows.append(S(x, y))
969            x += XS + XM
970        x, y = XS + XM * 3, (YS + h) // 2
971        for i in range(rows // 2):
972            self.s.rows.append(S(x, y))
973            x += XS + XM
974        self.setRegion(self.s.rows, (XS + XM, -999, 999999, 999999))
975
976        # create reserves
977        x, y = XM, YM + YS + self.TEXT_HEIGHT
978        for i in range(decks):
979            for i in range(reserves // decks):
980                self.s.reserves.append(S(x, y))
981                y += YS
982            x, y = x + XS, YM + YS * 4
983
984        # set window
985        self.size = (w, YM + YS + h)
986
987    #
988    # Ghulam layout
989    #  - left & right: foundations & reserves
990    #  - center: two groups of rows
991    #  - lower right: talon
992    #
993
994    def ghulamLayout(self, rows, reserves=0, texts=0):
995        S = self.__createStack
996        XM, YM = self.XM, self.YM
997        XS, YS = self.XS, self.YS
998
999        suits = len(self.game.gameinfo.suits)
1000        assert rows % 2 == 0
1001        assert reserves % 2 == 0
1002
1003        # set size
1004        w, h = XM * 3 + XS * ((rows // 2) + 2), YM + YS * ((suits // 2) + 2)
1005
1006        # create foundations
1007        x, y = XM, YM
1008        for i in range(suits):
1009            self.s.foundations.append(S(x, y, suit=i))
1010            y += YS
1011            if i == suits // 2 - 1:
1012                x, y = w - XS, YM
1013
1014        # create rows
1015        x = XM * 2 + XS
1016        for i in range(rows // 2):
1017            self.s.rows.append(S(x + i * XS, YM))
1018        for i in range(rows // 2):
1019            self.s.rows.append(S(x + i * XS, h // 2))
1020        self.setRegion(self.s.rows, (XM + XS, -999, w - XM - XS, 999999))
1021
1022        # create reserves
1023        for i in range(reserves // 2):
1024            self.s.reserves.append(S(XM, h - YS * (i + 1)))
1025        for i in range(reserves // 2):
1026            self.s.reserves.append(S(w - XS, h - YS * (i + 1)))
1027
1028        # create talon
1029        self.s.talon = S(w - XS * 2, h - YS)
1030        if texts:
1031            assert 0
1032
1033        # set window
1034        self.size = (w, h)
1035
1036    #
1037    # Generiklon layout
1038    #  - top: talon & foundations
1039    #  - bottom: rows
1040    #
1041
1042    def generiklonLayout(self, rows, waste=1, height=6):
1043        S = self.__createStack
1044        XM, YM = self.XM, self.YM
1045        XS, YS = self.XS, self.YS
1046
1047        decks = self.game.gameinfo.decks
1048        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)
1049        frows = suits * decks // 2
1050        fspace = XS * (rows - 1) // 2
1051
1052        # Set window size
1053        w, h = XM + XS * rows, YM * 2 + YS * height
1054        self.size = (w, h)
1055
1056        # Talon
1057        x, y = XM, YM
1058        self.s.talon = s = S(x, y)
1059        self._setText(s, 'se')
1060        self.s.waste = s = S(x, y + YS)
1061        self._setText(s, 'se')
1062
1063        # Create foundations
1064        x = w - fspace - XS * frows // 2
1065        for suit in range(suits // 2):
1066            for i in range(decks):
1067                self.s.foundations.append(S(x, y, suit=suit))
1068                x += XS
1069        x = w - fspace - XS * frows // 2
1070        y += YS
1071        for suit in range(suits // 2):
1072            for i in range(decks):
1073                self.s.foundations.append(S(x, y, suit=(suit + suits // 20)))
1074                x += XS
1075
1076        # bottom
1077        x, y = XM, YM * 2 + YS * 2
1078        for i in range(rows):
1079            self.s.rows.append(S(x, y))
1080            x += XS
1081        self.setRegion(self.s.rows, (-999, y - YM, 999999, 999999))
1082