line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Games::Cards::Undo; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# part of Games::Cards by Amir Karger (See Cards.pm for details) |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=pod |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 NAME |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
Games::Cards::Undo -- undoing/redoing moves in Games::Cards games |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 SYNOPSIS |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
use Games::Cards::Undo; |
14
|
|
|
|
|
|
|
$Undo = new Games::Cards::Undo(100); # Make undo engine to save 100 moves |
15
|
|
|
|
|
|
|
$Undo->undo; # undo last move |
16
|
|
|
|
|
|
|
$Undo->redo; # redo last undone move |
17
|
|
|
|
|
|
|
$Undo->end_move; # tell undo engine we're done with a move |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 DESCRIPTION |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
This is the package for methods to undo & redo moves. The GC::Undo object has |
22
|
|
|
|
|
|
|
no publicly accessible fields. But it stores an array of the |
23
|
|
|
|
|
|
|
preceding moves. Note that a "move" is made up of several "atoms" (objects of |
24
|
|
|
|
|
|
|
the private class GC::Undo::Atom and its subclassess). For example, moving a |
25
|
|
|
|
|
|
|
card from one column to another in solitaire involves one or more Splice atoms |
26
|
|
|
|
|
|
|
(removing or adding card(s) to a CardSet) and possibly a Face atom (turning a |
27
|
|
|
|
|
|
|
card over). |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
Many of the GC::Undo methods (and all of the GC::Undo::Atom methods) will be |
30
|
|
|
|
|
|
|
called by other Games::Cards methods, but not by the actual games. Here are |
31
|
|
|
|
|
|
|
the publicly accesssible methods: |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=over 4 |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
=cut |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# TODO write Undo::Sort? |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# sub-packages |
41
|
|
|
|
|
|
|
{ |
42
|
|
|
|
|
|
|
package Games::Cards::Undo; |
43
|
|
|
|
|
|
|
package Games::Cards::Undo::Atom; |
44
|
|
|
|
|
|
|
package Games::Cards::Undo::Splice; |
45
|
|
|
|
|
|
|
package Games::Cards::Undo::Face; |
46
|
|
|
|
|
|
|
package Games::Cards::Undo::End_Move; |
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
# How does Games::Cards handle undo? |
50
|
|
|
|
|
|
|
# |
51
|
|
|
|
|
|
|
# Undo_List is just an array of (objects from derived classes of) Undo::Atoms. |
52
|
|
|
|
|
|
|
# E.g. in solitaire one "move" might include moving cards from one column to |
53
|
|
|
|
|
|
|
# another (two Undo::Splice objects) and turning a card over (a Undo::Face |
54
|
|
|
|
|
|
|
# object) The undo list will store those Atoms as well as an End_Move object, |
55
|
|
|
|
|
|
|
# which is just a placeholder saying that move is over. |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# Global private variables |
58
|
|
|
|
|
|
|
# Can't keep this info in an object, because private GC subroutines |
59
|
|
|
|
|
|
|
# (like CardSet::splice) need access to the Undo list, and I shouldn't have |
60
|
|
|
|
|
|
|
# to pass the undo object around to every sub. |
61
|
|
|
|
|
|
|
# GC::Undo::Undo_List holds all previous moves in GC::Undo::Atom objects |
62
|
|
|
|
|
|
|
# GC::Undo::Current_Atom is the index of the current Atom in @Undo_List |
63
|
|
|
|
|
|
|
# GC::Undo::Max_Size is the maximum size (moves, not Atoms!) of the undo list |
64
|
|
|
|
|
|
|
# GC::Undo::In_Undo says that we're currently doing (or undoing) an Undo, so we |
65
|
|
|
|
|
|
|
# shouldn't store undo information when we move cards around |
66
|
|
|
|
|
|
|
my (@Undo_List, $Current_Atom, $Max_Size, $In_Undo); |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=item new(MOVES) |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Initialize the Undo engine. MOVES is the number of atoms to save. |
71
|
|
|
|
|
|
|
0 (or no argument) allows infinite undo. |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
This method must be called before any undo-able moves are made (i.e., it can be |
74
|
|
|
|
|
|
|
called after the hands are dealt). This method will also re-initialize the |
75
|
|
|
|
|
|
|
engine for a new game. |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=cut |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub new { |
80
|
0
|
|
|
0
|
1
|
0
|
my $class = shift; |
81
|
|
|
|
|
|
|
# (re)set global private variables |
82
|
0
|
|
0
|
|
|
0
|
$Max_Size = shift || 0; |
83
|
0
|
|
|
|
|
0
|
$Current_Atom = -1; |
84
|
0
|
|
|
|
|
0
|
@Undo_List = (); |
85
|
0
|
|
|
|
|
0
|
$In_Undo = 0; |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# Make the (dummy) object to give a "handle" for methods |
88
|
0
|
|
|
|
|
0
|
my $thing = {}; |
89
|
0
|
|
|
|
|
0
|
bless $thing, $class; |
90
|
0
|
|
|
|
|
0
|
return $thing; |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=item end_move |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
End the current move. Everything between the last call to end_move and now |
96
|
|
|
|
|
|
|
is considered one move. This tells undo how much to undo. |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=cut |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub end_move { |
101
|
|
|
|
|
|
|
# Don't store anything if no atoms have been stored since the |
102
|
|
|
|
|
|
|
# last End_Move atom. This could happen e.g. if someone does |
103
|
|
|
|
|
|
|
# an illegal move & then wants to undo it. |
104
|
0
|
0
|
0
|
0
|
1
|
0
|
if (! defined $Current_Atom || |
|
|
|
0
|
|
|
|
|
105
|
|
|
|
|
|
|
$Current_Atom == -1 || |
106
|
|
|
|
|
|
|
ref($Undo_List[$Current_Atom]) eq "Games::Cards::Undo::End_Move") { |
107
|
0
|
|
|
|
|
0
|
return; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# calling with just "store(foo)" there aren't enough args! |
111
|
0
|
|
|
|
|
0
|
my $atom = new Games::Cards::Undo::End_Move; |
112
|
0
|
|
|
|
|
0
|
$atom->store; |
113
|
|
|
|
|
|
|
} # end sub Games::Cards::Undo::end_move |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
sub store { |
116
|
|
|
|
|
|
|
# Stores a move in the undo list, which can later be undone or redone. The |
117
|
|
|
|
|
|
|
# first argument is the type of move to store, other args give details about |
118
|
|
|
|
|
|
|
# the move depending on the move type. |
119
|
|
|
|
|
|
|
# |
120
|
|
|
|
|
|
|
# arg1 is a subclass of Undo::Atom |
121
|
|
|
|
|
|
|
# Don't store moves if the undo engine hasn't been initialized |
122
|
1288
|
50
|
|
1288
|
0
|
9441
|
return unless defined $Current_Atom; |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
# don't store undo moves when we're currently implementing an undo/redo |
125
|
0
|
0
|
|
|
|
0
|
return if $In_Undo; |
126
|
|
|
|
|
|
|
|
127
|
0
|
|
|
|
|
0
|
shift; # ignore class |
128
|
0
|
|
|
|
|
0
|
my $atom = shift; # the Undo::Atom to store |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
# If we undid some moves & then do a new move instead of redoing, |
131
|
|
|
|
|
|
|
# then erase the moves we undid |
132
|
0
|
|
|
|
|
0
|
$#Undo_List = $Current_Atom; |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# Now add the move to the undo list |
135
|
0
|
|
|
|
|
0
|
push @Undo_List, $atom; |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# If the list is too big, remove a whole move (not just an Atom) |
138
|
|
|
|
|
|
|
# from the beginning of the list (oldest undos) |
139
|
0
|
|
|
|
|
0
|
my $end_class = "Games::Cards::Undo::End_Move"; |
140
|
0
|
0
|
0
|
|
|
0
|
if ($Max_Size && grep {ref eq $end_class} @Undo_List > $Max_Size) { |
|
0
|
|
|
|
|
0
|
|
141
|
0
|
|
|
|
|
0
|
$atom = shift @Undo_List until ref($atom) eq $end_class; |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
|
144
|
0
|
|
|
|
|
0
|
$Current_Atom = $#Undo_List; |
145
|
|
|
|
|
|
|
|
146
|
0
|
|
|
|
|
0
|
return 1; |
147
|
|
|
|
|
|
|
} # end sub Games::Cards::Undo::store |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
=item undo |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
Undo a move. |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=cut |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
sub undo { |
156
|
|
|
|
|
|
|
# undoing a move means undoing all the Atoms since the last |
157
|
|
|
|
|
|
|
# End_Move Atom |
158
|
|
|
|
|
|
|
# Note that this sub can (?) also undo from the middle of a move |
159
|
|
|
|
|
|
|
# If called w/ class instead of object, and we never called new(), |
160
|
|
|
|
|
|
|
# then return. This shouldn't happen. |
161
|
0
|
0
|
|
0
|
1
|
0
|
return unless defined $Current_Atom; # never called new |
162
|
0
|
0
|
|
|
|
0
|
return if $Current_Atom == -1; |
163
|
0
|
|
|
|
|
0
|
$In_Undo = 1; # Don't store info when moving cards around |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# Loop until the next End_Move Atom or until we exhaust the undo list |
166
|
0
|
|
|
|
|
0
|
my $end_class= "Games::Cards::Undo::End_Move"; |
167
|
0
|
0
|
|
|
|
0
|
$Current_Atom-- if ref($Undo_List[$Current_Atom]) eq $end_class; |
168
|
0
|
|
|
|
|
0
|
for (;$Current_Atom > -1; $Current_Atom--) { |
169
|
0
|
|
|
|
|
0
|
my $atom = $Undo_List[$Current_Atom]; |
170
|
0
|
0
|
|
|
|
0
|
last if ref($atom) eq $end_class; |
171
|
0
|
|
|
|
|
0
|
$atom->undo; |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
# now $Current_Atom is on the End_Move at the end of the last move |
174
|
|
|
|
|
|
|
|
175
|
0
|
|
|
|
|
0
|
$In_Undo = 0; # done undoing. Allowed to store again. |
176
|
0
|
|
|
|
|
0
|
return 1; |
177
|
|
|
|
|
|
|
} # end sub Games::Cards::Undo::undo |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=item redo |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
Redo a move that had been undone with undo. |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=cut |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
sub redo { |
187
|
|
|
|
|
|
|
# redoing a move means redoing every Atom from the current atom |
188
|
|
|
|
|
|
|
# (which should be an End_Move) until the next End_Move atom |
189
|
|
|
|
|
|
|
# If called w/ class instead of object, and we never called new(), |
190
|
|
|
|
|
|
|
# then return. This shouldn't happen. |
191
|
0
|
0
|
|
0
|
1
|
0
|
return unless defined $Current_Atom; |
192
|
0
|
0
|
|
|
|
0
|
return if $Current_Atom == $#Undo_List; |
193
|
0
|
|
|
|
|
0
|
$In_Undo = 1; # Don't store info when moving cards around |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# Loop until the next End_Move Atom or until we exhaust the undo list |
196
|
0
|
|
|
|
|
0
|
my $atom; |
197
|
0
|
|
|
|
|
0
|
my $end_class = "Games::Cards::Undo::End_Move"; |
198
|
0
|
0
|
|
|
|
0
|
$Current_Atom++ if ref($Undo_List[$Current_Atom]) eq $end_class; |
199
|
0
|
|
|
|
|
0
|
for (;$Current_Atom <= $#Undo_List; $Current_Atom++) { |
200
|
0
|
|
|
|
|
0
|
my $atom = $Undo_List[$Current_Atom]; |
201
|
0
|
0
|
|
|
|
0
|
last if ref($atom) eq $end_class; |
202
|
0
|
|
|
|
|
0
|
$atom->redo; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
# now $Current_Atom is on the End_Move at the end of this move |
205
|
|
|
|
|
|
|
|
206
|
0
|
|
|
|
|
0
|
$In_Undo = 0; # done redoing. Allowed to store again. |
207
|
0
|
|
|
|
|
0
|
return 1; |
208
|
|
|
|
|
|
|
} # end sub Games::Cards::Undo::redo |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=back |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=cut |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
{ |
215
|
|
|
|
|
|
|
package Games::Cards::Undo::Atom; |
216
|
|
|
|
|
|
|
# A CG::Undo::Atom object stores the smallest indivisible amount of undo |
217
|
|
|
|
|
|
|
# information. The subclasses of this class implement different kinds of atoms, |
218
|
|
|
|
|
|
|
# as well as the way to undo and redo them. |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub new { |
221
|
|
|
|
|
|
|
# This new will be used by subclasses |
222
|
|
|
|
|
|
|
# arg0 is the class. arg1 is a hashref containing various fields. Just |
223
|
|
|
|
|
|
|
# store 'em. |
224
|
1288
|
|
|
1288
|
|
1696
|
my $class = shift; |
225
|
1288
|
|
50
|
|
|
2590
|
my $atom = shift || {}; |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
# turn it into an undo move |
228
|
1288
|
|
|
|
|
4440
|
bless $atom, $class; |
229
|
|
|
|
|
|
|
} # end sub Games::Cards::Undo::Atom::new |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
sub store { |
232
|
|
|
|
|
|
|
# Store this Atom in the Undo List |
233
|
1288
|
|
|
1288
|
|
2649
|
Games::Cards::Undo->store(shift); |
234
|
|
|
|
|
|
|
} # end sub Games::Cards::Undo::Atom::store |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
} # end package Games::Cards::Undo::Atom |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
{ |
239
|
|
|
|
|
|
|
package Games::Cards::Undo::End_Move; |
240
|
|
|
|
|
|
|
# An Undo::End_Move is just a marker. Everything in the Undo_List from just |
241
|
|
|
|
|
|
|
# after the last End_Move until this one is one "move". |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
@Games::Cards::Undo::End_Move::ISA = qw(Games::Cards::Undo::Atom); |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# inherit SUPER::new |
246
|
|
|
|
|
|
|
# No other methods necessary! |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
} # end package Games::Cards::Undo::End_Move |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
{ |
251
|
|
|
|
|
|
|
package Games::Cards::Undo::Face; |
252
|
|
|
|
|
|
|
# This object stores the act of turning a card over |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
@Games::Cards::Undo::Face::ISA = qw(Games::Cards::Undo::Atom); |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
# inherit SUPER::new |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
sub undo { |
259
|
0
|
|
|
0
|
|
|
my $face = shift; |
260
|
0
|
|
|
|
|
|
my ($card, $direction) = ($face->{"card"}, $face->{"direction"}); |
261
|
0
|
0
|
|
|
|
|
if ($direction eq "up") { |
|
|
0
|
|
|
|
|
|
262
|
0
|
|
|
|
|
|
$card->face_down; |
263
|
|
|
|
|
|
|
} elsif ($direction eq "down") { |
264
|
0
|
|
|
|
|
|
$card->face_up; |
265
|
|
|
|
|
|
|
} else { |
266
|
0
|
|
|
|
|
|
my $func = (caller(0))[3]; |
267
|
0
|
|
|
|
|
|
die ("$func called with unknown direction $direction\n"); |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
} # end sub Games::Cards::Undo::Face::undo |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
sub redo { |
272
|
0
|
|
|
0
|
|
|
my $face = shift; |
273
|
0
|
|
|
|
|
|
my ($card, $direction) = ($face->{"card"}, $face->{"direction"}); |
274
|
0
|
0
|
|
|
|
|
if ($direction eq "up") { |
|
|
0
|
|
|
|
|
|
275
|
0
|
|
|
|
|
|
$card->face_up; |
276
|
|
|
|
|
|
|
} elsif ($direction eq "down") { |
277
|
0
|
|
|
|
|
|
$card->face_down; |
278
|
|
|
|
|
|
|
} else { |
279
|
0
|
|
|
|
|
|
my $func = (caller(0))[3]; |
280
|
0
|
|
|
|
|
|
die ("$func called with unknown direction $direction\n"); |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
} # end sub Games::Cards::Undo::Face::redo |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
} # end package Games::Cards::Undo::Face |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
{ |
287
|
|
|
|
|
|
|
package Games::Cards::Undo::Splice; |
288
|
|
|
|
|
|
|
# This object stores the act of adding or removing cards from a CardSet, i.e. |
289
|
|
|
|
|
|
|
# one of these objects gets created each time GC::CardSet::splice is called. |
290
|
|
|
|
|
|
|
# This stores most of the actions in a card game. |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
@Games::Cards::Undo::Splice::ISA = qw(Games::Cards::Undo::Atom); |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
# inherit SUPER::new |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
sub undo { |
297
|
|
|
|
|
|
|
# If we changed ARRAY by doing: |
298
|
|
|
|
|
|
|
# RESULT = splice(ARRAY, OFFSET, LENGTH, LIST); |
299
|
|
|
|
|
|
|
# then we can return ARRAY to its original form by |
300
|
|
|
|
|
|
|
# splice(ARRAY, OFFSET, scalar(LIST), RESULT); |
301
|
|
|
|
|
|
|
# |
302
|
|
|
|
|
|
|
# (sub splice also made sure that for calls to splice without |
303
|
|
|
|
|
|
|
# all the arguments, the missing arguments were added, and that OFFSET |
304
|
|
|
|
|
|
|
# would be >= 0) |
305
|
|
|
|
|
|
|
|
306
|
0
|
|
|
0
|
|
|
my $splice = shift; |
307
|
|
|
|
|
|
|
# Could do this quicket with no strict refs :) |
308
|
0
|
|
|
|
|
|
my ($set, $offset, $in_cards, $out_cards) = |
309
|
0
|
|
|
|
|
|
map {$splice->{$_}} qw(set offset in_cards out_cards); |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
# Do the anti-splice and return its return value |
312
|
|
|
|
|
|
|
# (Return will actually be in_cards!) |
313
|
0
|
|
|
|
|
|
$set->splice ($offset, scalar(@$in_cards), $out_cards); |
314
|
|
|
|
|
|
|
} # end sub Cards::Games::Undo::Splice::undo |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
sub redo { |
317
|
|
|
|
|
|
|
# we changed ARRAY by doing: |
318
|
|
|
|
|
|
|
# RESULT = splice(ARRAY, OFFSET, LENGTH, LIST); |
319
|
|
|
|
|
|
|
# Just redo the splice. |
320
|
|
|
|
|
|
|
# (sub splice also made sure that for calls to splice without |
321
|
|
|
|
|
|
|
# all the arguments, the missing arguments were added, and that OFFSET |
322
|
|
|
|
|
|
|
# would be >= 0) |
323
|
|
|
|
|
|
|
|
324
|
0
|
|
|
0
|
|
|
my $splice = shift; |
325
|
|
|
|
|
|
|
# Could do this quicket with no strict refs :) |
326
|
0
|
|
|
|
|
|
my ($set, $offset, $in_cards, $length) = |
327
|
0
|
|
|
|
|
|
map {$splice->{$_}} qw(set offset in_cards length); |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
# Do the splice and return its return value |
330
|
|
|
|
|
|
|
# (Return will actually be out_cards!) |
331
|
0
|
|
|
|
|
|
$set->splice ($offset, $length, $in_cards); |
332
|
|
|
|
|
|
|
} # end sub Cards::Games::Undo::Splice::redo |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
} # end package Games::Cards::Undo::Splice |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
1; # end package Games::Cards::Undo |