├── README.md ├── game ├── card │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ ├── 17.png │ ├── 18.png │ ├── 19.png │ ├── 2.png │ ├── 20.png │ ├── 21.png │ ├── 22.png │ ├── 23.png │ ├── 24.png │ ├── 25.png │ ├── 26.png │ ├── 27.png │ ├── 28.png │ ├── 29.png │ ├── 3.png │ ├── 30.png │ ├── 31.png │ ├── 32.png │ ├── 33.png │ ├── 34.png │ ├── 35.png │ ├── 36.png │ ├── 37.png │ ├── 38.png │ ├── 39.png │ ├── 4.png │ ├── 40.png │ ├── 41.png │ ├── 42.png │ ├── 43.png │ ├── 44.png │ ├── 45.png │ ├── 46.png │ ├── 47.png │ ├── 48.png │ ├── 49.png │ ├── 5.png │ ├── 50.png │ ├── 51.png │ ├── 52.png │ ├── 53.png │ ├── 54.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ ├── 9.png │ ├── back.png │ └── base.png ├── cardgame.rpy ├── eileen_happy.png ├── klondike.rpy ├── mainmenu.jpg ├── options.rpy ├── script.rpy └── table.jpg └── index.rst /README.md: -------------------------------------------------------------------------------- 1 | Ren'Py Cardgame Framework 2 | ========================= 3 | 4 | Cardgame is a framework that provides primitives for creating cardgames with 5 | Ren’Py. Formatted documentation for this framework can be found at: 6 | 7 | http://renpy-cardgame.readthedocs.org/ 8 | 9 | -------------------------------------------------------------------------------- /game/card/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/1.png -------------------------------------------------------------------------------- /game/card/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/10.png -------------------------------------------------------------------------------- /game/card/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/11.png -------------------------------------------------------------------------------- /game/card/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/12.png -------------------------------------------------------------------------------- /game/card/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/13.png -------------------------------------------------------------------------------- /game/card/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/14.png -------------------------------------------------------------------------------- /game/card/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/15.png -------------------------------------------------------------------------------- /game/card/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/16.png -------------------------------------------------------------------------------- /game/card/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/17.png -------------------------------------------------------------------------------- /game/card/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/18.png -------------------------------------------------------------------------------- /game/card/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/19.png -------------------------------------------------------------------------------- /game/card/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/2.png -------------------------------------------------------------------------------- /game/card/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/20.png -------------------------------------------------------------------------------- /game/card/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/21.png -------------------------------------------------------------------------------- /game/card/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/22.png -------------------------------------------------------------------------------- /game/card/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/23.png -------------------------------------------------------------------------------- /game/card/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/24.png -------------------------------------------------------------------------------- /game/card/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/25.png -------------------------------------------------------------------------------- /game/card/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/26.png -------------------------------------------------------------------------------- /game/card/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/27.png -------------------------------------------------------------------------------- /game/card/28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/28.png -------------------------------------------------------------------------------- /game/card/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/29.png -------------------------------------------------------------------------------- /game/card/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/3.png -------------------------------------------------------------------------------- /game/card/30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/30.png -------------------------------------------------------------------------------- /game/card/31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/31.png -------------------------------------------------------------------------------- /game/card/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/32.png -------------------------------------------------------------------------------- /game/card/33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/33.png -------------------------------------------------------------------------------- /game/card/34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/34.png -------------------------------------------------------------------------------- /game/card/35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/35.png -------------------------------------------------------------------------------- /game/card/36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/36.png -------------------------------------------------------------------------------- /game/card/37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/37.png -------------------------------------------------------------------------------- /game/card/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/38.png -------------------------------------------------------------------------------- /game/card/39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/39.png -------------------------------------------------------------------------------- /game/card/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/4.png -------------------------------------------------------------------------------- /game/card/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/40.png -------------------------------------------------------------------------------- /game/card/41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/41.png -------------------------------------------------------------------------------- /game/card/42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/42.png -------------------------------------------------------------------------------- /game/card/43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/43.png -------------------------------------------------------------------------------- /game/card/44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/44.png -------------------------------------------------------------------------------- /game/card/45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/45.png -------------------------------------------------------------------------------- /game/card/46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/46.png -------------------------------------------------------------------------------- /game/card/47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/47.png -------------------------------------------------------------------------------- /game/card/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/48.png -------------------------------------------------------------------------------- /game/card/49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/49.png -------------------------------------------------------------------------------- /game/card/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/5.png -------------------------------------------------------------------------------- /game/card/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/50.png -------------------------------------------------------------------------------- /game/card/51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/51.png -------------------------------------------------------------------------------- /game/card/52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/52.png -------------------------------------------------------------------------------- /game/card/53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/53.png -------------------------------------------------------------------------------- /game/card/54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/54.png -------------------------------------------------------------------------------- /game/card/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/6.png -------------------------------------------------------------------------------- /game/card/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/7.png -------------------------------------------------------------------------------- /game/card/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/8.png -------------------------------------------------------------------------------- /game/card/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/9.png -------------------------------------------------------------------------------- /game/card/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/back.png -------------------------------------------------------------------------------- /game/card/base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/card/base.png -------------------------------------------------------------------------------- /game/cardgame.rpy: -------------------------------------------------------------------------------- 1 | # cardgame.rpy - Cardgame support for Ren'Py 2 | # 3 | # Copyright 2008-2015 Tom Rothamel 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation files 7 | # (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, merge, 9 | # publish, distribute, sublicense, and/or sell copies of the Software, 10 | # and to permit persons to whom the Software is furnished to do so, 11 | # subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | init python: 25 | 26 | import pygame 27 | 28 | DRAG_NONE = 0 29 | DRAG_CARD = 1 30 | DRAG_ABOVE = 2 31 | DRAG_STACK = 3 32 | DRAG_TOP = 4 33 | 34 | # Returns the overlap of the area between the two 35 | # rectangles. 36 | def __rect_overlap_area(r1, r2): 37 | if r1 is None or r2 is None: 38 | return 0 39 | 40 | x1, y1, w1, h1 = r1 41 | x2, y2, w2, h2 = r2 42 | 43 | maxleft = max(x1, x2) 44 | minright = min(x1 + w1, x2 + w2) 45 | maxtop = max(y1, y2) 46 | minbottom = min(y1 + h1, y2 + h2) 47 | 48 | if minright < maxleft: 49 | return 0 50 | 51 | if minbottom < maxtop: 52 | return 0 53 | 54 | return (minright - maxleft) * (minbottom - maxtop) 55 | 56 | def __default_can_drag(table, stack, card): 57 | return table.get_faceup(card) 58 | 59 | class Table(renpy.Displayable): 60 | 61 | def __init__(self, back=None, base=None, springback=0.1, rotate=0.1, can_drag=__default_can_drag, doubleclick=.33, **kwargs): 62 | 63 | renpy.Displayable.__init__(self, **kwargs) 64 | 65 | # A map from card value to the card object corresponding to 66 | # that value. 67 | self.cards = { } 68 | 69 | # A list of the stacks that have been defined. 70 | self.stacks = [ ] 71 | 72 | # The back of cards that don't have a more specific back 73 | # defined. 74 | self.back = renpy.easy.displayable_or_none(back) 75 | 76 | # The base of stacks that don't have a more specific base 77 | # defined. 78 | self.base = renpy.easy.displayable_or_none(base) 79 | 80 | # The amount of time it takes for cards to springback 81 | # into their rightful place. 82 | self.springback = springback 83 | 84 | # The amount of time it takes for cards to rotate into 85 | # their proper orientation. 86 | self.rotate = rotate 87 | 88 | # A function that is called to tell if we can drag a 89 | # particular card. 90 | self.can_drag = can_drag 91 | 92 | # The time between clicks for the click to be considered a 93 | # double-click. 94 | self.doubleclick = doubleclick 95 | 96 | # Are we sensitive to input? [doc] 97 | self.sensitive = True 98 | 99 | # The last click event. 100 | self.last_event = CardEvent() 101 | 102 | # The card that has been clicked. 103 | self.click_card = None 104 | 105 | # The stack that has been clicked. 106 | self.click_stack = None 107 | 108 | # The list of cards that are being dragged. 109 | self.drag_cards = [ ] 110 | 111 | # Are we dragging the cards? 112 | self.dragging = False 113 | 114 | # The position where we clicked. 115 | self.click_x = 0 116 | self.click_y = 0 117 | 118 | # The amount of time we've been shown for. 119 | self.st = 0 120 | 121 | # This shows the table on the given layer. 122 | def show(self, layer='master'): 123 | 124 | for v in self.cards.itervalues(): 125 | v.offset = __Fixed(0, 0) 126 | 127 | ui.layer(layer) 128 | ui.add(self) 129 | ui.close() 130 | 131 | # This hides the table. 132 | def hide(self, layer='master'): 133 | ui.layer(layer) 134 | ui.remove(self) 135 | ui.close() 136 | 137 | # This controls sensitivity. 138 | def set_sensitive(self, value): 139 | self.sensitive = value 140 | 141 | def get_card(self, value): 142 | if value not in self.cards: 143 | raise Exception("No card has the value %r." % value) 144 | 145 | return self.cards[value] 146 | 147 | # This sets the faceup status of a card. 148 | def set_faceup(self, card, faceup=True): 149 | self.get_card(card).faceup = faceup 150 | renpy.redraw(self, 0) 151 | 152 | def get_faceup(self, card): 153 | return self.get_card(card).faceup 154 | 155 | # This sets the rotation of a card. 156 | def set_rotate(self, card, rotation): 157 | __Rotate(self.get_card(card), rotation) 158 | renpy.redraw(self, 0) 159 | 160 | def get_rotate(self, card): 161 | return self.get_card(card).rotate.rotate_limit() 162 | 163 | def add_marker(self, card, marker): 164 | self.get_card(card).markers.append(marker) 165 | renpy.redraw(self, 0) 166 | 167 | def remove_marker(self, card, marker): 168 | self.get_card(card).markers.remove(marker) 169 | renpy.redraw(self, 0) 170 | 171 | # Called to create a new card. 172 | def card(self, value, face, back=None): 173 | self.cards[value] = __Card(self, value, face, back) 174 | 175 | # Called to create a new stack. 176 | def stack(self, x, y, xoff=0, yoff=0, show=1024, base=None, 177 | click=False, drag=DRAG_NONE, drop=False, hidden=False): 178 | 179 | rv = __Stack(self, x, y, xoff, yoff, show, base, click, drag, drop, hidden) 180 | 181 | self.stacks.append(rv) 182 | return rv 183 | 184 | # Force a redraw on each interaction. 185 | def per_interact(self): 186 | renpy.redraw(self, 0) 187 | 188 | 189 | def render(self, width, height, st, at): 190 | 191 | self.st = st 192 | 193 | rv = renpy.Render(width, height) 194 | 195 | for s in self.stacks: 196 | 197 | if s.hidden: 198 | s.rect = None 199 | for c in s.cards: 200 | c.rect = None 201 | continue 202 | 203 | s.render_to(rv, width, height, st, at) 204 | 205 | for c in s.cards: 206 | c.render_to(rv, width, height, st, at) 207 | 208 | return rv 209 | 210 | def event(self, ev, x, y, st): 211 | 212 | self.st = st 213 | 214 | if not self.sensitive: 215 | return 216 | 217 | grabbed = renpy.display.focus.get_grab() 218 | 219 | if (grabbed is not None) and (grabbed is not self): 220 | return 221 | 222 | if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1: 223 | 224 | if self.click_stack: 225 | return 226 | 227 | stack = None 228 | card = None 229 | 230 | for s in self.stacks: 231 | 232 | sx, sy, sw, sh = s.rect 233 | if sx <= x and sy <= y and sx + sw > x and sy + sh > y: 234 | stack = s 235 | 236 | 237 | for c in s.cards[-s.show:]: 238 | if c.rect is None: 239 | continue 240 | 241 | cx, cy, cw, ch = c.rect 242 | if cx <= x and cy <= y and cx + cw > x and cy + ch > y: 243 | card = c 244 | stack = c.stack 245 | 246 | if stack is None: 247 | return 248 | 249 | # Grab the display. 250 | renpy.display.focus.set_grab(self) 251 | 252 | # Don't let the user grab a moving card. 253 | if card is not None: 254 | xoffset, yoffset = card.offset.offset() 255 | if xoffset or yoffset: 256 | return 257 | 258 | # Move the stack containing the card to the front. 259 | self.stacks.remove(stack) 260 | self.stacks.append(stack) 261 | 262 | if stack.click or stack.drag: 263 | self.click_card = card 264 | self.click_stack = stack 265 | 266 | if card is None or not self.can_drag(self, card.stack, card.value): 267 | self.drag_cards = [ ] 268 | elif card.stack.drag == DRAG_CARD: 269 | self.drag_cards = [ card ] 270 | elif card.stack.drag == DRAG_ABOVE: 271 | self.drag_cards = [ ] 272 | for c in card.stack.cards: 273 | if c is card or self.drag_cards: 274 | self.drag_cards.append(c) 275 | elif card.stack.drag == DRAG_STACK: 276 | self.drag_cards = list(card.stack.cards) 277 | elif card.stack.drag == DRAG_TOP: 278 | if card.stack.cards[-1] is card: 279 | self.drag_cards = [ card ] 280 | else: 281 | self.drag_cards = [ ] 282 | 283 | for c in self.drag_cards: 284 | c.offset = __Fixed(0, 0) 285 | 286 | self.click_x = x 287 | self.click_y = y 288 | self.dragging = False 289 | 290 | renpy.redraw(self, 0) 291 | 292 | raise renpy.IgnoreEvent() 293 | 294 | if ev.type == pygame.MOUSEMOTION or (ev.type == pygame.MOUSEBUTTONUP and ev.button == 1): 295 | 296 | if abs(x - self.click_x) > 7 or abs(y - self.click_y) > 7: 297 | self.dragging = True 298 | 299 | dx = x - self.click_x 300 | dy = y - self.click_y 301 | 302 | for c in self.drag_cards: 303 | xoffset, yoffset = c.offset.offset() 304 | 305 | cdx = dx - xoffset 306 | cdy = dy - yoffset 307 | 308 | c.offset = __Fixed(dx, dy) 309 | 310 | if c.rect: 311 | cx, cy, cw, ch = c.rect 312 | cx += cdx 313 | cy += cdy 314 | c.rect = (cx, cy, cw, ch) 315 | 316 | area = 0 317 | dststack = None 318 | dstcard = None 319 | 320 | for s in self.stacks: 321 | if not s.drop: 322 | continue 323 | 324 | for c in self.drag_cards: 325 | 326 | if c.stack == s: 327 | continue 328 | a = __rect_overlap_area(c.rect, s.rect) 329 | if a >= area: 330 | dststack = s 331 | dstcard = None 332 | area = a 333 | 334 | for c1 in s.cards: 335 | a = __rect_overlap_area(c.rect, c1.rect) 336 | if a >= area: 337 | dststack = s 338 | dstcard = c1 339 | area = a 340 | 341 | if area == 0: 342 | dststack = None 343 | dstcard = None 344 | 345 | renpy.redraw(self, 0) 346 | 347 | if ev.type == pygame.MOUSEMOTION: 348 | raise renpy.IgnoreEvent() 349 | 350 | if ev.type == pygame.MOUSEBUTTONUP and ev.button == 1: 351 | 352 | # Ungrab the display. 353 | renpy.display.focus.set_grab(None) 354 | 355 | evt = None 356 | 357 | if self.dragging: 358 | if dststack is not None and self.drag_cards: 359 | 360 | evt = CardEvent() 361 | evt.type = "drag" 362 | evt.table = self 363 | evt.stack = self.click_stack 364 | evt.card = self.click_card.value 365 | evt.drag_cards = [c.value for c in self.drag_cards] 366 | evt.drop_stack = dststack 367 | if dstcard: 368 | evt.drop_card = dstcard.value 369 | evt.time = st 370 | 371 | else: 372 | 373 | if self.click_stack.click: 374 | 375 | evt = CardEvent() 376 | evt.type = "click" 377 | evt.table = self 378 | evt.stack = self.click_stack 379 | if self.click_card: 380 | evt.card = self.click_card.value 381 | else: 382 | evt.card = None 383 | 384 | evt.time = st 385 | 386 | if (evt.type == self.last_event.type 387 | and evt.stack == self.last_event.stack 388 | and evt.card == self.last_event.card 389 | and evt.time < self.last_event.time + self.doubleclick): 390 | 391 | evt.type = "doubleclick" 392 | 393 | if evt is not None: 394 | self.last_event = evt 395 | 396 | for c in self.drag_cards: 397 | c.springback() 398 | 399 | self.click_card = None 400 | self.click_stack = None 401 | self.drag_cards = [ ] 402 | 403 | if evt is not None: 404 | return evt 405 | else: 406 | raise renpy.IgnoreEvent() 407 | 408 | 409 | class CardEvent(object): 410 | 411 | def __init__(self): 412 | self.type = None 413 | self.stack = None 414 | self.card = None 415 | self.drag_cards = None 416 | self.drop_stack = None 417 | self.drop_card = None 418 | self.time = 0 419 | 420 | # Represents a stack of one or more cards, which can be placed on the 421 | # table. 422 | class __Stack(object): 423 | 424 | def __init__( 425 | self, table, 426 | x, y, 427 | xoff, yoff, 428 | show, base, 429 | click, drag, drop, 430 | hidden): 431 | 432 | 433 | # The table this stack belongs to. 434 | self.table = table 435 | 436 | # The x and y coordinates of the center of the top card of 437 | # this stack. 438 | self.x = x 439 | self.y = y 440 | 441 | # The offset in the x and y directions of each successive 442 | # card. 443 | self.xoff = xoff 444 | self.yoff = yoff 445 | 446 | # The number of cards to show from this stack. (We show the 447 | # last show cards if this is less than the numebr of cards 448 | # in the stack.) 449 | self.show = show 450 | 451 | # The image that is shown behind the stack. If None, the 452 | # background is taken from the table. 453 | self.base = base 454 | 455 | # Should we report click events on this stack? 456 | self.click = click 457 | 458 | # Should we allow dragging this stack? If so, how? 459 | self.drag = drag 460 | 461 | # Should we allow dropping to this stack? 462 | self.drop = drop 463 | 464 | # Is this stack hidden? 465 | self.hidden = hidden 466 | 467 | # The list of cards in this stack. 468 | self.cards = [ ] 469 | 470 | # The rectangle for the background of this effect. 471 | self.rect = None 472 | 473 | def insert(self, index, card): 474 | card = self.table.get_card(card) 475 | 476 | if card.stack: 477 | card.stack.cards.remove(card) 478 | 479 | card.stack = self 480 | self.cards.insert(index, card) 481 | 482 | self.table.stacks.remove(self) 483 | self.table.stacks.append(self) 484 | 485 | card.springback() 486 | 487 | def append(self, card): 488 | if card in self.cards: 489 | self.insert(len(self.cards) - 1, card) 490 | else: 491 | self.insert(len(self.cards), card) 492 | 493 | def remove(self, card): 494 | card = self.table.get_card(card) 495 | self.cards.remove(card) 496 | card.stack = None 497 | card.rect = None 498 | 499 | def deal(self): 500 | if not self.cards: 501 | return None 502 | 503 | card = self.cards[-1] 504 | self.remove(card.value) 505 | return card.value 506 | 507 | def shuffle(self): 508 | renpy.random.shuffle(self.cards) 509 | renpy.redraw(self.table, 0) 510 | 511 | def __len__(self): 512 | return len(self.cards) 513 | 514 | def __getitem__(self, idx): 515 | return self.cards[idx].value 516 | 517 | def __iter__(self): 518 | for i in self.cards: 519 | yield i.value 520 | 521 | def __contains__(self, item): 522 | return self.table.get_card(card) in self.cards 523 | 524 | def render_to(self, rv, width, height, st, at): 525 | 526 | base = self.base or self.table.base 527 | 528 | if base is None: 529 | return 530 | 531 | surf = renpy.render(base, width, height, st, at) 532 | cw, ch = surf.get_size() 533 | 534 | cx = self.x - cw / 2 535 | cy = self.y - ch / 2 536 | 537 | self.rect = (cx, cy, cw, ch) 538 | rv.blit(surf, (cx, cy)) 539 | 540 | class __Card(object): 541 | 542 | def __init__(self, table, value, face, back): 543 | 544 | # The table this card belongs to. 545 | self.table = table 546 | 547 | # The value of this card. 548 | self.value = value 549 | 550 | # The face of this card. 551 | self.face = renpy.easy.displayable(face) 552 | 553 | # The back of this card. If None, then the back is taken from 554 | # the table the card belongs to. 555 | self.back = renpy.easy.displayable_or_none(back) 556 | 557 | # Is this card faceup (or face down?) 558 | self.faceup = True 559 | 560 | # Object that's called to decide how rotated this card should 561 | # be. 562 | self.rotate = None 563 | 564 | # A series of markers that should be drawn over this card. 565 | self.markers = [ ] 566 | 567 | # The stack this card is in. 568 | self.stack = None 569 | 570 | # An object that gives the offset of this card relative to 571 | # where it would normally be placed. 572 | self.offset = __Fixed(0, 0) 573 | 574 | # The rectangle where this card was last drawn to the screen 575 | # at. 576 | self.rect = None 577 | 578 | __Rotate(self, 0) 579 | 580 | # Returns the base x and y placement of this card. 581 | def place(self): 582 | s = self.stack 583 | offset = max(len(s.cards) - s.show, 0) 584 | index = max(s.cards.index(self) - offset, 0) 585 | 586 | return (s.x + s.xoff * index, s.y + s.yoff * index) 587 | 588 | def springback(self): 589 | if self.rect is None: 590 | self.offset = __Fixed(0, 0) 591 | else: 592 | self.offset = __Springback(self) 593 | 594 | def render_to(self, rv, width, height, st, at): 595 | 596 | x, y = self.place() 597 | xoffset, yoffset = self.offset.offset() 598 | x += xoffset 599 | y += yoffset 600 | 601 | if self.faceup: 602 | d = self.face 603 | else: 604 | d = self.back or self.table.back 605 | 606 | # TODO: Figure out if we can reuse some of this. 607 | 608 | if self.markers: 609 | d = Fixed(* ([d] + [renpy.easy.displayable(i) for i in self.markers])) 610 | 611 | r = self.rotate.rotate() 612 | if r: 613 | d = RotoZoom(r, r, 0, 1, 1, 0)(d) 614 | 615 | surf = renpy.render(d, width, height, st, at) 616 | w, h = surf.get_size() 617 | 618 | x -= w / 2 619 | y -= h / 2 620 | 621 | self.rect = (x, y, w, h) 622 | 623 | rv.blit(surf, (x, y)) 624 | 625 | def __repr__(self): 626 | return "<__Card %r>" % self.value 627 | 628 | 629 | class __Springback(object): 630 | 631 | def __init__(self, card): 632 | self.card = card 633 | self.table = table = card.table 634 | 635 | self.start = table.st 636 | 637 | cx, cy, cw, ch = self.card.rect 638 | x = cx + cw / 2 639 | y = cy + ch / 2 640 | 641 | self.startx = x 642 | self.starty = y 643 | 644 | def offset(self): 645 | 646 | t = (self.table.st - self.start) / self.table.springback 647 | t = min(t, 1.0) 648 | 649 | if t < 1.0: 650 | renpy.redraw(self.table, 0) 651 | 652 | px, py = self.card.place() 653 | 654 | return int((self.startx - px) * (1.0 - t)), int((self.starty - py) * (1.0 - t)) 655 | 656 | 657 | class __Fixed(object): 658 | def __init__(self, x, y): 659 | self.x = x 660 | self.y = y 661 | 662 | def offset(self): 663 | return self.x, self.y 664 | 665 | 666 | class __Rotate(object): 667 | def __init__(self, card, amount): 668 | 669 | self.table = table = card.table 670 | self.start = table.st 671 | 672 | if card.rotate is None: 673 | self.start_rotate = amount 674 | else: 675 | self.start_rotate = card.rotate.rotate() 676 | 677 | self.end_rotate = amount 678 | 679 | card.rotate = self 680 | 681 | 682 | def rotate(self): 683 | 684 | if self.start_rotate == self.end_rotate: 685 | return self.start_rotate 686 | 687 | t = (self.table.st - self.start) / self.table.springback 688 | t = min(t, 1.0) 689 | 690 | if t < 1.0: 691 | renpy.redraw(self.table, 0) 692 | 693 | return self.start_rotate + (self.end_rotate - self.start_rotate) * t 694 | 695 | def rotate_limit(self): 696 | return self.end_rotate 697 | -------------------------------------------------------------------------------- /game/eileen_happy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/eileen_happy.png -------------------------------------------------------------------------------- /game/klondike.rpy: -------------------------------------------------------------------------------- 1 | # klondike.rpy - Klondike Solitaire 2 | # 3 | # Copyright 2008-2015 Tom Rothamel 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation files 7 | # (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, merge, 9 | # publish, distribute, sublicense, and/or sell copies of the Software, 10 | # and to permit persons to whom the Software is furnished to do so, 11 | # subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | 25 | init python: 26 | 27 | class Klondike(object): 28 | 29 | # We represent a card as a (suit, rank) tuple. The suit is one of the 30 | # following four constants, while the rank is 1 for ace, 2 for 2, 31 | # ..., 10 for 10, 11 for jack, 12 for queen, 13 for king. 32 | CLUB = 0 33 | SPADE = 1 34 | HEART = 2 35 | DIAMOND = 3 36 | 37 | def __init__(self, deal=3): 38 | 39 | # Constants that let us easily change where the game is 40 | # located. 41 | LEFT=140 42 | TOP=58 43 | COL_SPACING = 90 44 | ROW_SPACING = 120 45 | CARD_XSPACING = 20 46 | CARD_YSPACING = 30 47 | 48 | # Store the parameters. 49 | self.deal = deal 50 | 51 | # Create the table, stock, and waste. 52 | self.table = t = Table(base="card/base.png", back="card/back.png") 53 | self.stock = t.stack(LEFT, TOP, xoff=0, yoff=0, click=True) 54 | self.waste = t.stack(LEFT + COL_SPACING, TOP, xoff=CARD_XSPACING, drag=DRAG_TOP, show=self.deal, click=True) 55 | 56 | # The 4 foundation stacks. 57 | self.foundations = [ ] 58 | for i in range(0, 4): 59 | s = t.stack(LEFT + COL_SPACING * (i + 3), TOP, xoff=0, yoff=0, drag=DRAG_TOP, drop=True) 60 | self.foundations.append(s) 61 | 62 | # The 7 tableau stacks. 63 | self.tableau = [ ] 64 | for i in range(0, 7): 65 | s = t.stack(LEFT + COL_SPACING * i, TOP + ROW_SPACING, xoff=0, yoff=CARD_YSPACING, drag=DRAG_ABOVE, click=True, drop=True) 66 | self.tableau.append(s) 67 | 68 | # Create the stock and shuffle it. 69 | for rank in range(1, 14): 70 | for suit in range(0, 4): 71 | value = (suit, rank) 72 | t.card(value, "card/%d.png" % self.card_num(suit, rank)) 73 | t.set_faceup(value, False) 74 | self.stock.append(value) 75 | 76 | self.stock.shuffle() 77 | 78 | # Deal out the initial tableau. 79 | for i in range(0, 7): 80 | for j in range(i, 7): 81 | c = self.stock.deal() 82 | self.tableau[j].append(c) 83 | 84 | # Ensure that the bottom of each tableau is faceup. 85 | for i in range(0, 7): 86 | if self.tableau[i]: 87 | self.table.set_faceup(self.tableau[i][-1], True) 88 | 89 | 90 | # This figures out the image filename for a given suit and rank. 91 | def card_num(self, suit, rank): 92 | ranks = [ None, 1, 49, 45, 41, 37, 33, 29, 25, 21, 17, 13, 9, 5 ] 93 | return suit + ranks[rank] 94 | 95 | def show(self): 96 | self.table.show() 97 | 98 | def hide(self): 99 | self.table.hide() 100 | 101 | def tableau_drag(self, evt): 102 | 103 | card = evt.drag_cards[0] 104 | cards = evt.drag_cards 105 | stack = evt.drop_stack 106 | 107 | csuit, crank = card 108 | 109 | # If the stack is empty, allow a king to be dragged to it. 110 | if not stack: 111 | if crank == 13: 112 | for i in cards: 113 | stack.append(i) 114 | 115 | return "tableau_drag" 116 | 117 | # Otherwise, the stack has a bottom card. 118 | bottom = stack[-1] 119 | bsuit, brank = bottom 120 | 121 | # Figure out which of the stacks are black. 122 | cblack = (csuit == self.SPADE) or (csuit == self.CLUB) 123 | bblack = (bsuit == self.SPADE) or (bsuit == self.CLUB) 124 | 125 | # Can we legally place the cards? 126 | if (bblack != cblack) and (crank == brank - 1): 127 | 128 | # Place the cards: 129 | for i in cards: 130 | stack.append(i) 131 | 132 | return "tableau_drag" 133 | 134 | return False 135 | 136 | def foundation_drag(self, evt): 137 | 138 | # We can only drag one card at a time to a foundation. 139 | if len(evt.drag_cards) != 1: 140 | return False 141 | 142 | suit, rank = evt.drag_cards[0] 143 | 144 | # If there is a card on the foundation already, then 145 | # check to see if we're dropping then next one in 146 | # sequence. 147 | if len(evt.drop_stack): 148 | dsuit, drank = evt.drop_stack[-1] 149 | if suit == dsuit and rank == drank + 1: 150 | evt.drop_stack.append(evt.drag_cards[0]) 151 | return "foundation_drag" 152 | 153 | # Otherwise, make sure we're dropping an ace. 154 | else: 155 | if rank == 1: 156 | evt.drop_stack.append(evt.drag_cards[0]) 157 | return "foundation_drag" 158 | 159 | return False 160 | 161 | def tableau_doubleclick(self, evt): 162 | 163 | # Make sure that there's at least one card in the stack. 164 | if not evt.stack: 165 | return False 166 | 167 | # The bottom card in the stack. 168 | card = evt.stack[-1] 169 | suit, rank = card 170 | 171 | # If the card is an ace, find an open foundation and put it 172 | # there. 173 | if rank == 1: 174 | for i in self.foundations: 175 | if not i: 176 | i.append(card) 177 | break 178 | return "foundation_drag" 179 | 180 | # Otherwise, see if there's a foundation where we can put 181 | # the card. 182 | for i in self.foundations: 183 | if not i: 184 | continue 185 | 186 | fsuit, frank = i[-1] 187 | if suit == fsuit and rank == frank + 1: 188 | i.append(card) 189 | return "foundation_drag" 190 | 191 | return False 192 | 193 | def stock_click(self, evt): 194 | 195 | # If there are cards in the stock, dispense up to three3 196 | # of them. 197 | if self.stock: 198 | for i in range(0, self.deal): 199 | if self.stock: 200 | c = self.stock[-1] 201 | self.table.set_faceup(c, True) 202 | self.waste.append(c) 203 | 204 | return "stock_click" 205 | 206 | # Otherwise, move the contents of the waste to the stock. 207 | else: 208 | while self.waste: 209 | c = self.waste[-1] 210 | self.table.set_faceup(c, False) 211 | self.stock.append(c) 212 | 213 | return "stock_click" 214 | 215 | 216 | def interact(self): 217 | 218 | evt = ui.interact() 219 | rv = False 220 | 221 | # Check the various events, and dispatch them to the methods 222 | # that handle them. 223 | if evt.type == "drag": 224 | if evt.drop_stack in self.tableau: 225 | rv = self.tableau_drag(evt) 226 | 227 | elif evt.drop_stack in self.foundations: 228 | rv = self.foundation_drag(evt) 229 | 230 | elif evt.type == "click": 231 | if evt.stack == self.stock: 232 | rv = self.stock_click(evt) 233 | 234 | elif evt.type == "doubleclick": 235 | if (evt.stack in self.tableau) or (evt.stack is self.waste): 236 | rv = self.tableau_doubleclick(evt) 237 | 238 | # Ensure that the bottom card in each tableau is faceup. 239 | for i in range(0, 7): 240 | if self.tableau[i]: 241 | self.table.set_faceup(self.tableau[i][-1], True) 242 | 243 | # Check to see if any of the foundations has less than 244 | # 13 cards in it. If it does, return False. Otherwise, 245 | # return True. 246 | for i in self.foundations: 247 | if len(i) != 13: 248 | return rv 249 | 250 | return "win" 251 | 252 | # Sets things as sensitive (or not). 253 | def set_sensitive(self, value): 254 | self.table.set_sensitive(value) 255 | 256 | # Utility functions. 257 | 258 | # Is it okay to drag the over card onto under, where under is 259 | # part of a tableau. 260 | def can_hint(self, under, over): 261 | usuit, urank = under 262 | osuit, orank = over 263 | 264 | if orank == 1: 265 | return False 266 | 267 | ublack = (usuit == self.SPADE) or (usuit == self.CLUB) 268 | oblack = (osuit == self.SPADE) or (osuit == self.CLUB) 269 | 270 | if (oblack != ublack) and (orank == urank - 1): 271 | return True 272 | 273 | # Returns the first faceup card in the stack. 274 | def first_faceup(self, s): 275 | for c in s: 276 | if self.table.get_faceup(c): 277 | return c 278 | 279 | # This tries to find a reasonable hint, and returns it as a 280 | # pair of cardnames. 281 | def hint(self): 282 | 283 | for i in self.tableau: 284 | if not i: 285 | continue 286 | 287 | over = self.first_faceup(i) 288 | 289 | for j in self.tableau: 290 | if not j or i is j: 291 | continue 292 | 293 | under = j[-1] 294 | 295 | if self.can_hint(under, over): 296 | return (under, over) 297 | 298 | if self.waste: 299 | 300 | over = self.waste[-1] 301 | 302 | for j in self.tableau: 303 | if not j: 304 | continue 305 | 306 | under = j[-1] 307 | 308 | if self.can_hint(under, over): 309 | return (under, over) 310 | 311 | return None, None 312 | 313 | def card_name(self, c): 314 | suit, rank = c 315 | 316 | return [ 317 | "INVALID", 318 | "Ace", 319 | "Two", 320 | "Three", 321 | "four", 322 | "Five", 323 | "Six", 324 | "Seven", 325 | "Eight", 326 | "Nine", 327 | "Ten", 328 | "Jack", 329 | "Queen", 330 | "King" ][rank] + " of " + [ 331 | "Clubs", 332 | "Spades", 333 | "Hearts", 334 | "Diamonds" ][suit] 335 | 336 | 337 | -------------------------------------------------------------------------------- /game/mainmenu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/mainmenu.jpg -------------------------------------------------------------------------------- /game/options.rpy: -------------------------------------------------------------------------------- 1 | ## This file contains some of the options that can be changed to customize 2 | ## your Ren'Py game. It only contains the most common options... there 3 | ## is quite a bit more customization you can do. 4 | ## 5 | ## Lines beginning with two '#' marks are comments, and you shouldn't 6 | ## uncomment them. Lines beginning with a single '#' mark are 7 | ## commented-out code, and you may want to uncomment them when 8 | ## appropriate. 9 | 10 | init -1 python hide: 11 | 12 | ## Should we enable the use of developer tools? This should be 13 | ## set to False before the game is released, so the user can't 14 | ## cheat using developer tools. 15 | 16 | config.developer = True 17 | 18 | ## These control the width and height of the screen. 19 | 20 | config.screen_width = 800 21 | config.screen_height = 600 22 | 23 | ## This controls the title of the window, when Ren'Py is 24 | ## running in a window. 25 | 26 | config.window_title = u"Ren'Py Cardgame Demo" 27 | 28 | ######################################### 29 | # Layouts 30 | 31 | ## This enables the use of an in-game menu that is made out of 32 | ## buttons. 33 | 34 | layout.button_menu() 35 | 36 | ######################################### 37 | # Themes 38 | 39 | ## We then want to call a theme function. themes.roundrect is 40 | ## a theme that features the use of rounded rectangles. It's 41 | ## the only theme we currently support. 42 | ## 43 | ## The theme function takes a number of parameters that can 44 | ## customize the color scheme. 45 | 46 | theme.roundrect( 47 | 48 | ## The color of an idle widget face. 49 | widget = "#7AA27B", 50 | 51 | ## The color of a focused widget face. 52 | widget_hover = "#A3C7A3", 53 | 54 | ## The color of the text in a widget. 55 | widget_text = "#CDE0CE", 56 | 57 | ## The color of the text in a selected widget. (For 58 | ## example, the current value of a preference.) 59 | widget_selected = "#ffffff", 60 | 61 | ## The color of a disabled widget face. 62 | disabled = "#426143", 63 | 64 | ## The color of disabled widget text. 65 | disabled_text = "#819981", 66 | 67 | ## The color of informational labels. 68 | label = "#ffffff", 69 | 70 | ## The color of a frame containing widgets. 71 | frame = "#245536", 72 | 73 | ## If this is True, the in-game window is rounded. If False, 74 | ## the in-game window is square. 75 | rounded_window = False, 76 | 77 | ## The background of the main menu. This can be a color 78 | ## beginning with '#', or an image filename. The latter 79 | ## should take up the full height and width of the screen. 80 | mm_root = "mainmenu.jpg", 81 | 82 | ## The background of the game menu. This can be a color 83 | ## beginning with '#', or an image filename. The latter 84 | ## should take up the full height and width of the screen. 85 | gm_root = "table.jpg", 86 | 87 | ## And we're done with the theme. The theme will customize 88 | ## various styles, so if we want to change them, we should 89 | ## do so below. 90 | ) 91 | 92 | 93 | ######################################### 94 | ## These settings let you customize the window containing the 95 | ## dialogue and narration, by replacing it with an image. 96 | 97 | ## The background of the window. In a Frame, the two numbers 98 | ## are the size of the left/right and top/bottom borders, 99 | ## respectively. 100 | 101 | # style.window.background = Frame("frame.png", 12, 12) 102 | 103 | ## Margin is space surrounding the window, where the background 104 | ## is not drawn. 105 | 106 | # style.window.left_margin = 6 107 | # style.window.right_margin = 6 108 | # style.window.top_margin = 6 109 | # style.window.bottom_margin = 6 110 | 111 | ## Padding is space inside the window, where the background is 112 | ## drawn. 113 | 114 | # style.window.left_padding = 6 115 | # style.window.right_padding = 6 116 | # style.window.top_padding = 6 117 | # style.window.bottom_padding = 6 118 | 119 | ## This is the minimum height of the window, including the margins 120 | ## and padding. 121 | 122 | # style.window.yminimum = 250 123 | 124 | 125 | ######################################### 126 | ## This lets you change the placement of the main menu. 127 | 128 | ## The way placement works is that we find an anchor point 129 | ## inside a displayable, and a position (pos) point on the 130 | ## screen. We then place the displayable so the two points are 131 | ## at the same place. 132 | 133 | ## An anchor/pos can be given as an integer or a floating point 134 | ## number. If an integer, the number is interpreted as a number 135 | ## of pixels from the upper-left corner. If a floating point, 136 | ## the number is interpreted as a fraction of the size of the 137 | ## displayable or screen. 138 | 139 | # style.mm_menu_frame.xpos = 0.5 140 | # style.mm_menu_frame.xanchor = 0.5 141 | # style.mm_menu_frame.ypos = 0.75 142 | # style.mm_menu_frame.yanchor = 0.5 143 | 144 | 145 | ######################################### 146 | ## These let you customize the default font used for text in Ren'Py. 147 | 148 | ## The file containing the default font. 149 | 150 | # style.default.font = "DejaVuSans.ttf" 151 | 152 | ## The default size of text. 153 | 154 | # style.default.size = 22 155 | 156 | ## Note that these only change the size of some of the text. Other 157 | ## buttons have their own styles. 158 | 159 | 160 | ######################################### 161 | ## These settings let you change some of the sounds that are used by 162 | ## Ren'Py. 163 | 164 | ## Set this to False if the game does not have any sound effects. 165 | 166 | config.has_sound = True 167 | 168 | ## Set this to False if the game does not have any music. 169 | 170 | config.has_music = True 171 | 172 | ## Set this to False if the game does not have voicing. 173 | 174 | config.has_voice = True 175 | 176 | ## Sounds that are used when button and imagemaps are clicked. 177 | 178 | # style.button.activate_sound = "click.wav" 179 | # style.imagemap.activate_sound = "click.wav" 180 | 181 | ## Sounds that are used when entering and exiting the game menu. 182 | 183 | # config.enter_sound = "click.wav" 184 | # config.exit_sound = "click.wav" 185 | 186 | ## A sample sound that can be played to check the sound volume. 187 | 188 | # config.sample_sound = "click.wav" 189 | 190 | ## Music that is played while the user is at the main menu. 191 | 192 | # config.main_menu_music = "main_menu_theme.ogg" 193 | 194 | 195 | ######################################### 196 | ## Transitions. 197 | 198 | ## Used when entering the game menu from the game. 199 | 200 | config.enter_transition = dissolve 201 | 202 | ## Used when exiting the game menu to the game. 203 | 204 | config.exit_transition = dissolve 205 | 206 | ## Used between screens of the game menu. 207 | 208 | config.intra_transition = dissolve 209 | 210 | ## Used when entering the game menu from the main menu. 211 | 212 | config.main_game_transition = dissolve 213 | 214 | ## Used when returning to the main menu from the game. 215 | 216 | config.game_main_transition = dissolve 217 | 218 | ## Used when entering the main menu from the splashscreen. 219 | 220 | config.end_splash_transition = dissolve 221 | 222 | ## Used when entering the main menu after the game has ended. 223 | 224 | config.end_game_transition = fade 225 | 226 | ## Used when a game is loaded. 227 | 228 | config.after_load_transition = dissolve 229 | 230 | 231 | ######################################### 232 | ## Default values of Preferences. 233 | 234 | ## Note: These options are only evaluated the first time a 235 | ## game is run. To have them run a second time, delete 236 | ## game/saves/persistent 237 | 238 | ## Should we start in fullscreen mode? 239 | 240 | config.default_fullscreen = False 241 | 242 | ## The default text speed in characters per second. 0 is infinite. 243 | 244 | config.default_text_cps = 0 245 | 246 | config.save_directory = "cardgame-11754" 247 | 248 | -------------------------------------------------------------------------------- /game/script.rpy: -------------------------------------------------------------------------------- 1 | # You can place the script of your game in this file. 2 | 3 | init: 4 | $ e = Character('Eileen', color="#c8ffc8") 5 | 6 | image eileen happy = "eileen_happy.png" 7 | image bg table = "table.jpg" 8 | image dim = "#0008" 9 | 10 | # Some styles for show text. 11 | $ style.centered_text.drop_shadow = (2, 2) 12 | $ style.centered_text.drop_shadow_color = "#000b" 13 | 14 | label start: 15 | 16 | scene bg table 17 | 18 | python: 19 | k = Klondike(1) 20 | k.set_sensitive(False) 21 | k.show() 22 | 23 | show dim 24 | show eileen happy 25 | with dissolve 26 | 27 | e "Welcome to the cardgame demo. Let's play some solitaire!" 28 | 29 | e "I might show up from time to time to give you some advice, but it's up to you if you want to take it." 30 | 31 | e "Good luck!" 32 | 33 | label continue: 34 | 35 | hide dim 36 | hide eileen 37 | with dissolve 38 | 39 | label quick_continue: 40 | 41 | $ hint_count = renpy.random.randint(10, 20) 42 | 43 | while True: 44 | 45 | python: 46 | 47 | ui.textbutton("Give Up", ui.jumps("giveup"), xalign=.02, yalign=.98) 48 | k.set_sensitive(True) 49 | event = k.interact() 50 | 51 | if event: 52 | renpy.checkpoint() 53 | 54 | if event == "win": 55 | jump win 56 | 57 | if event == "tableau_drag" or event == "stock_click": 58 | $ hint_count -= 1 59 | if hint_count <= 0: 60 | jump hint 61 | 62 | label win: 63 | 64 | show dim 65 | show eileen happy 66 | with dissolve 67 | 68 | "Congratulations!" 69 | 70 | jump newgame 71 | 72 | label giveup: 73 | 74 | $ k.set_sensitive(False) 75 | 76 | show dim 77 | show eileen happy 78 | with dissolve 79 | 80 | menu: 81 | e "Are you sure you want to give up?" 82 | 83 | "Yes": 84 | 85 | "Oh well, better luck next time." 86 | 87 | jump newgame 88 | 89 | "No": 90 | 91 | jump continue 92 | 93 | label newgame: 94 | 95 | menu: 96 | e "Would you like to try again?" 97 | 98 | "Yes": 99 | pass 100 | 101 | "No": 102 | e "Well, I hope to see you again soon." 103 | return 104 | 105 | e "Okay, here we go!" 106 | 107 | scene bg table 108 | 109 | python: 110 | k = Klondike(1) 111 | k.sensitive = False 112 | k.show() 113 | 114 | jump continue 115 | 116 | 117 | label hint: 118 | 119 | $ under, over = k.hint() 120 | 121 | $ print under, over 122 | 123 | if under is None: 124 | jump quick_continue 125 | 126 | $ under = k.card_name(under) 127 | $ over = k.card_name(over) 128 | 129 | $ k.set_sensitive(False) 130 | 131 | show dim 132 | show eileen happy 133 | with dissolve 134 | 135 | $ hint = renpy.random.randint(0, 2) 136 | 137 | if hint == 0: 138 | e "Maybe put the %(over)s on top of the %(under)s." 139 | 140 | elif hint == 1: 141 | e "You can try moving the %(over)s to the %(under)s." 142 | 143 | elif hint == 2: 144 | e "I think something can go on the %(under)s." 145 | 146 | jump continue 147 | 148 | 149 | # This has nothing to do with card games. 150 | label splashscreen: 151 | 152 | scene bg table 153 | $ renpy.pause(1.0) 154 | 155 | show text "According to legend, prospectors in the klondike would carry with them a deck of cards." 156 | with dissolve 157 | with Pause(5.0) 158 | hide text 159 | with dissolve 160 | with Pause(1.0) 161 | 162 | show text "If they were ever lost, they'd start playing a game of solitaire." 163 | with dissolve 164 | with Pause(4.0) 165 | hide text 166 | with dissolve 167 | with Pause(1.0) 168 | 169 | show text "Without fail, help would soon arrive..." 170 | with dissolve 171 | with Pause(3.0) 172 | hide text 173 | with dissolve 174 | with Pause(1.0) 175 | 176 | show text "... saying \"put the Five of Spades on the Six of Hearts.\"" 177 | 178 | with dissolve 179 | with Pause(4.0) 180 | hide text 181 | with dissolve 182 | with Pause(1.0) 183 | 184 | return 185 | -------------------------------------------------------------------------------- /game/table.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renpy/cardgame/460e48cf065e5a913b1a1c4dc54f9e29679cdd5e/game/table.jpg -------------------------------------------------------------------------------- /index.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Ren'Py Cardgame Framework 3 | ========================= 4 | 5 | Cardgame is a framework that provides primitives for creating cardgames 6 | with Ren'Py. The cardgame engine does not actually implement any card 7 | games for you. Instead, it implements a set of primitives that are common 8 | to various card games: 9 | 10 | * Cards 11 | * Stacks of cards 12 | * Clicking and double-clicking on a card or stack. 13 | * Dragging cards between stacks 14 | * Flipping cards over 15 | 16 | Providing these primitives makes it easy to implement card games. (For 17 | example, an implementation of klondike solitaire takes 225 18 | lines of code, including comments and blank lines.) 19 | 20 | Downloading 21 | =========== 22 | 23 | Cardgame is now maintained on github at: 24 | 25 | https://github.com/renpy/cardgame 26 | 27 | It can be downloaded using git, or by clicking the "Download Zip" button 28 | at the bottom of the right column of that github link. 29 | 30 | License 31 | ======= 32 | 33 | Cardgame is licensed under the following terms, which are open source and 34 | free for commercial use. :: 35 | 36 | Permission is hereby granted, free of charge, to any person 37 | obtaining a copy of this software and associated documentation files 38 | (the "Software"), to deal in the Software without restriction, 39 | including without limitation the rights to use, copy, modify, merge, 40 | publish, distribute, sublicense, and/or sell copies of the Software, 41 | and to permit persons to whom the Software is furnished to do so, 42 | subject to the following conditions: 43 | 44 | The above copyright notice and this permission notice shall be 45 | included in all copies or substantial portions of the Software. 46 | 47 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 48 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 49 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 50 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 51 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 52 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 53 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 54 | 55 | Concepts 56 | ======== 57 | 58 | There are four concepts that are used by cardgame: tables, cards, stacks, and card events. 59 | 60 | Table 61 | Table objects are the fundamental displayable used by cardgame. A table may 62 | have multiple stacks and multiple cards defined underneath it, and takes 63 | care of displaying cards and stacks, and producing events in response to 64 | user input. 65 | 66 | Tables should be displayed on the master layer using the show and hide 67 | methods. Adding a table to the transient layer may lead to confused card 68 | motion animation. 69 | 70 | Tables do not display a background image underneath the stacks. 71 | Such an image can be displayed using the scene or show statements. 72 | 73 | Card 74 | Cardgame represents cards using hashable values provided by the user. 75 | Unlike tables, stacks, and events, cards are not objects produced by 76 | the cardgame, but instead can be objects or other values produced by the 77 | user. For example, the eight of spades might be represented as the 78 | tuple (1, 8). User-defined objects are also often acceptable. 79 | 80 | Cards may be face-up or face-down. A card has two images associated with 81 | it: a face and a back. A card may either be part of a single stack, 82 | or may not belong to any stack. A card is only displayed if it belongs to 83 | a stack. 84 | 85 | Stack 86 | A stack represents a group of one or more cards. Each stack has a position 87 | relative to the table, and cards are displayed at offsets relative to that 88 | position. Stacks support many of the methods of a list, and also a few 89 | cardgame-specific methods. Stacks have a base image that is shown 90 | when the stack is empty. 91 | 92 | CardEvent 93 | When a table is shown and sensitive, CardEvent objects will be 94 | created in response to user input. These objects store information 95 | about the event. 96 | 97 | Table 98 | ===== 99 | 100 | .. class:: Table(back=None, base=None, springback=0.1, can_drag=..., doubleclick=.33) 101 | 102 | Creates a new Table. 103 | 104 | `base` 105 | A displayable giving an image used as the base of stacks that do not specify a more specific base. 106 | `springback` 107 | The amount of time it takes for cards to spring to their new location when they have been moved between stacks, or dropped. 108 | `can_drag` 109 | A function that should return true if a card can be dragged. The function is given three arguments, the table, stack, and card that are being dragged. It should return True to allow the drag, or False otherwise. The default function returns True if the card being dragged is face up. 110 | `doubleclick` 111 | The maximum time between clicks for a doubleclick event to be recognized. 112 | 113 | 114 | .. method:: Table.card(value, face, back) 115 | 116 | Declares a new card. 117 | 118 | `value` 119 | A hashable value that is used to represent this card. 120 | `face` 121 | A displayable that's used for the face of this card. 122 | `back` 123 | If not None, a displayable that's used for the back of the card. Otherwise, the value of the back argument to 124 | the Table constructor is used. 125 | 126 | 127 | .. method:: stack(x, y, xoff=0, yoff=0, show=1024, base=None, click=False, drag=DRAG_NONE, drop=False, hidden=False) 128 | 129 | Declares a new stack of cards, and returns the Stack object. 130 | 131 | `x`, `y` 132 | Give the x and y offsets of the center of the bottom-most card of the 133 | stack, or the base of the stack if no card exists. 134 | `xoff`, `yoff` 135 | The offsets of each card in the stack relative to the next-lower card in the stack. 136 | `show` 137 | The number of cards to show from the stack. If there are more than 138 | this number of cards in the stack, only the topmost show cards are shown. 139 | `base` 140 | An image that is used for the base of the stack, if no cards are in 141 | the stack. If this is None, the default base specified with the 142 | Table is used. 143 | `clicked` 144 | If True, this stack will return "click" and "doubleclick" events when the stack is clicked. 145 | `drag` 146 | Sets which cards, if any, participate in drags from this stack: 147 | 148 | * DRAG_NONE - No dragging can occur. 149 | * DRAG_CARD - Only the card being dragged will be dragged. 150 | * DRAG_ABOVE - The card being dragged and all cards above it will be dragged. 151 | * DRAG_STACK - All cards in the stack will be dragged. 152 | * DRAG_TOP - The top card in the stack will be dragged. 153 | `drop` 154 | If True, this stack can be used as a drop target. 155 | `hidden` 156 | If True, this stack will not be shown on the screen. 157 | 158 | .. method:: show(layer='master') 159 | 160 | Shows this table on `layer`. 161 | 162 | .. method:: hide(layer='master') 163 | 164 | Hides this table from `layer` 165 | 166 | .. method:: set_sensitive(value) 167 | 168 | Determines if this table will respond to events. If value is false, 169 | the table will stop responding to events until this is called with 170 | `value` true. 171 | 172 | .. method:: set_faceup(card, faceup=True) 173 | 174 | Determines if `card` will be displayed face up or face down. The card 175 | is displayed face up if `faceup` is True. 176 | 177 | .. method:: get_faceup(card) 178 | 179 | Returns True if the `card` is faceup and False otherwise. 180 | 181 | .. method:: set_rotate(card, rotation) 182 | 183 | Sets the rotation of `card` to `rotation` degrees. Rotation quality 184 | leaves something to be desired. 185 | 186 | .. method:: get_rotate(card) 187 | 188 | Returns the rotation of `card`, in degrees. 189 | 190 | .. method:: add_marker(card, marker) 191 | 192 | Adds a marker to the card. `marker` should be a Displayable. 193 | 194 | .. method:: remove_marker(card, marker): 195 | 196 | Removes `marker` from `card`. 197 | 198 | Stack 199 | ===== 200 | 201 | .. class:: Stack 202 | 203 | Stack objects represent stacks of cards, and are created by the Table.stack 204 | method. 205 | 206 | Stack support basic list operations, like len(), indexing, membership tests, 207 | and iteration. Cards are placed in the list in bottom to top order. 208 | 209 | Stack objects also support the following methods: 210 | 211 | .. method:: insert(index, card) 212 | 213 | Inserts `card` in the stack at index, where 0 is the bottom of the stack 214 | and len(s) is the top of the stack. If the card is in a stack, animates 215 | the card moving to the new stack. 216 | 217 | .. method:: append(card) 218 | 219 | Places `card` on the top of the stack. If the card is in a stack, 220 | animates the card moving to the top of the stack. 221 | 222 | .. method:: remove(card) 223 | 224 | Removes card from the stack. 225 | 226 | .. method:: deal() 227 | 228 | Removes the card at the top of the stack from the stack, and returns it. Returns None if the stack is empty. 229 | 230 | .. method:: shuffle() 231 | 232 | Rearranges the cards in the stack in a random order. 233 | 234 | CardEvent 235 | ========= 236 | 237 | .. class:: CardEvent 238 | 239 | CardEvent objects are returned from ui.interact() when events happen while a 240 | Table is sensitive. All event objects have the following fields defined: 241 | 242 | type 243 | One of "drag", "click", or "doubleclick", giving the type of event this is. 244 | table 245 | The table this event is associated with. 246 | stack 247 | The stack that has been clicked or dragged from. 248 | card 249 | The card that has been clicked or dragged. None for a click on the stack. 250 | 251 | Drag events also have the following fields defined. 252 | 253 | drag_cards 254 | A list of cards being dragged. 255 | drop_stack 256 | The stack the cards are being dropped on. 257 | drop_card 258 | The card the cards are being dropped on, if any, If this is None, the 259 | cards were dropped onto an empty stack. 260 | --------------------------------------------------------------------------------