Free cookie consent management tool by TermsFeed Policy Generator

source: branches/SimulationCore/HeuristicLab.SimulationCore.Samples/3.3/CardGameSimulation.cs @ 10454

Last change on this file since 10454 was 10454, checked in by abeham, 10 years ago

#1610: updated core, implemented card game sample

File size: 22.6 KB
RevLine 
[10454]1using System;
2using System.Collections.Generic;
3using System.Linq;
4using HeuristicLab.Common;
5using HeuristicLab.Core;
6using HeuristicLab.Data;
7using HeuristicLab.Parameters;
8using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
9using HeuristicLab.Random;
10
11namespace HeuristicLab.SimulationCore.Samples {
12  #region Model
13  public enum Suits { Hearts, Bells, Leaves, Acorns }
14  public enum Faces { Numeric, Under, Over, King, Sow }
15
16  [StorableClass]
17  public sealed class Card : IDeepCloneable {
18    [Storable]
19    public Suits Suit { get; private set; }
20    [Storable]
21    public Faces Face { get; private set; }
22    [Storable]
23    public int Value { get; private set; }
24
25    [StorableConstructor]
26    private Card(bool deserializing) { }
27    private Card(Card original, Cloner cloner) {
28      cloner.RegisterClonedObject(original, this);
29      Suit = original.Suit;
30      Face = original.Face;
31      Value = original.Value;
32    }
33    public Card(Suits s, Faces f, int v) {
34      Suit = s;
35      Face = f;
36      Value = v;
37    }
38
39    public object Clone() { return Clone(new Cloner()); }
40    public IDeepCloneable Clone(Cloner cloner) {
41      return new Card(this, cloner);
42    }
43
44    public override bool Equals(object obj) {
45      var other = (obj as Card);
46      if (other == null) return false;
47      return Suit == other.Suit && Face == other.Face && Value == other.Value;
48    }
49
50    public override int GetHashCode() {
51      return Suit.GetHashCode() ^ Face.GetHashCode() ^ Value.GetHashCode();
52    }
53  }
54
55  [StorableClass]
56  public sealed class Deck : IDeepCloneable {
57    private List<Card> cards;
58    public IEnumerable<Card> Cards { get { return cards.Select(x => x); } }
59
60    public int Count { get { return cards.Count; } }
61
62    [StorableConstructor]
63    private Deck(bool deserializing) { }
64    private Deck(Deck original, Cloner cloner) {
65      cloner.RegisterClonedObject(original, this);
66      cards = original.Cards.Select(cloner.Clone).ToList();
67    }
68    public Deck() {
69      cards = new List<Card>() {
70        new Card(Suits.Acorns, Faces.Numeric, 7),
71        new Card(Suits.Acorns, Faces.Numeric, 8),
72        new Card(Suits.Acorns, Faces.Numeric, 9),
73        new Card(Suits.Acorns, Faces.Numeric, 10),
74        new Card(Suits.Acorns, Faces.Under, 10),
75        new Card(Suits.Acorns, Faces.Over, 10),
76        new Card(Suits.Acorns, Faces.King, 10),
77        new Card(Suits.Acorns, Faces.Sow, 11), 
78        new Card(Suits.Bells, Faces.Numeric, 7),
79        new Card(Suits.Bells, Faces.Numeric, 8),
80        new Card(Suits.Bells, Faces.Numeric, 9),
81        new Card(Suits.Bells, Faces.Numeric, 10),
82        new Card(Suits.Bells, Faces.Under, 10),
83        new Card(Suits.Bells, Faces.Over, 10),
84        new Card(Suits.Bells, Faces.King, 10),
85        new Card(Suits.Bells, Faces.Sow, 11), 
86        new Card(Suits.Hearts, Faces.Numeric, 7),
87        new Card(Suits.Hearts, Faces.Numeric, 8),
88        new Card(Suits.Hearts, Faces.Numeric, 9),
89        new Card(Suits.Hearts, Faces.Numeric, 10),
90        new Card(Suits.Hearts, Faces.Under, 10),
91        new Card(Suits.Hearts, Faces.Over, 10),
92        new Card(Suits.Hearts, Faces.King, 10),
93        new Card(Suits.Hearts, Faces.Sow, 11),
94        new Card(Suits.Leaves, Faces.Numeric, 7),
95        new Card(Suits.Leaves, Faces.Numeric, 8),
96        new Card(Suits.Leaves, Faces.Numeric, 9),
97        new Card(Suits.Leaves, Faces.Numeric, 10),
98        new Card(Suits.Leaves, Faces.Under, 10),
99        new Card(Suits.Leaves, Faces.Over, 10),
100        new Card(Suits.Leaves, Faces.King, 10),
101        new Card(Suits.Leaves, Faces.Sow, 11),
102      };
103    }
104
105    public object Clone() { return Clone(new Cloner()); }
106    public IDeepCloneable Clone(Cloner cloner) {
107      return new Deck(this, cloner);
108    }
109
110    public void Shuffle() {
111      cards = cards.Shuffle(new FastRandom()).ToList();
112    }
113
114    public Card Deal1() {
115      var deal = cards[cards.Count - 1];
116      cards.RemoveAt(cards.Count - 1);
117      return deal;
118    }
119
120    public Card[] Deal3() {
121      var deal = new Card[3];
122      deal[0] = cards[cards.Count - 3];
123      deal[1] = cards[cards.Count - 2];
124      deal[2] = cards[cards.Count - 1];
125      cards.RemoveRange(cards.Count - 3, 3);
126      return deal;
127    }
128  }
129
130  [StorableClass]
131  public sealed class Hand : IDeepCloneable {
132    [Storable]
133    public Card First { get; set; }
134    [Storable]
135    public Card Second { get; set; }
136    [Storable]
137    public Card Third { get; set; }
138
139    public IEnumerable<Card> Cards {
140      get { return new[] { First, Second, Third }; }
141      set {
142        var v = value.ToArray();
143        First = v[0];
144        Second = v[1];
145        Third = v[2];
146        if (v.Length != 3) throw new ArgumentException("A hand must always consist of exactly three cards.");
147      }
148    }
149
150    [StorableConstructor]
151    private Hand(bool deserializing) { }
152    private Hand(Hand original, Cloner cloner) {
153      cloner.RegisterClonedObject(original, this);
154      Cards = original.Cards.Select(cloner.Clone);
155    }
156    public Hand(IEnumerable<Card> cards) {
157      Cards = cards;
158    }
159
160    public object Clone() { return Clone(new Cloner()); }
161    public IDeepCloneable Clone(Cloner cloner) {
162      return new Hand(this, cloner);
163    }
164
165    public int Rating() {
166      if (First.Suit == Second.Suit && First.Suit == Third.Suit) {
167        var value = First.Value + Second.Value + Third.Value;
168        if (value == 31) return 36;
169        return value;
170      }
171      if (First.Suit == Second.Suit) return First.Value + Second.Value;
172      if (First.Suit == Third.Suit) return First.Value + Third.Value;
173      if (Second.Suit == Third.Suit) return Second.Value + Third.Value;
174      if (Cards.Select(x => x.Face).Distinct().Count() == 1) return 33;
175      return 0;
176    }
177  }
178
179  [StorableClass]
180  public sealed class Player : IDeepCloneable {
181    [Storable]
182    public string Name { get; private set; }
183    [Storable]
184    public Hand Hand { get; set; }
185    [Storable]
186    public int Coins { get; set; }
187    [Storable]
188    public bool Blocks { get; set; }
189
190    [StorableConstructor]
191    private Player(bool deserializing) { }
192    private Player(Player original, Cloner cloner) {
193      cloner.RegisterClonedObject(original, this);
194      Name = original.Name;
195      Hand = cloner.Clone(original.Hand);
196      Coins = original.Coins;
197      Blocks = original.Blocks;
198    }
199    public Player(string name, int coins) {
200      Name = name;
201      Coins = coins;
202      Blocks = false;
203    }
204
205    public object Clone() { return Clone(new Cloner()); }
206    public IDeepCloneable Clone(Cloner cloner) {
207      return new Player(this, cloner);
208    }
209  }
210
211  [StorableClass]
212  public sealed class CardGameModel : Model {
213    [Storable]
214    public Hand Table { get; set; }
215    [Storable]
216    public Hand Talon { get; set; }
217    [Storable]
218    public List<Player> Players { get; set; }
219    [Storable]
220    public Deck Deck { get; set; }
221    [Storable]
222    public int DealerIndex { get; set; }
223    [Storable]
224    public int PlayerIndex { get; set; }
225    [Storable]
226    public IRandom Random { get; set; }
227
228    [StorableConstructor]
229    private CardGameModel(bool deserializing) : base(deserializing) { }
230    private CardGameModel(CardGameModel original, Cloner cloner)
231      : base(original, cloner) {
232      Table = cloner.Clone(original.Table);
233      Talon = cloner.Clone(original.Talon);
234      if (original.Players != null) Players = original.Players.Select(cloner.Clone).ToList();
235      Deck = cloner.Clone(original.Deck);
236      DealerIndex = original.DealerIndex;
237      PlayerIndex = original.PlayerIndex;
238      Random = cloner.Clone(original.Random);
239    }
240    public CardGameModel() { }
241
242    public override IDeepCloneable Clone(Cloner cloner) {
243      return new CardGameModel(this, cloner);
244    }
245  }
246  #endregion
247
248  #region Simulation
249  [StorableClass]
250  [Creatable("Simulations")]
251  public sealed class CardGameSimulation : DiscreteEventSimulation<CardGameModel> {
252    public override Type ProblemType {
253      get { return typeof(CardGameScenario); }
254    }
255
256    private CardGameScenario Scenario {
257      get { return (CardGameScenario)base.Problem; }
258      set { base.Problem = value; }
259    }
260
261    [StorableConstructor]
262    private CardGameSimulation(bool deserializing) : base(deserializing) { }
263    private CardGameSimulation(CardGameSimulation original, Cloner cloner)
264      : base(original, cloner) {
265      RegisterScenarioEventHandlers();
266    }
267    public CardGameSimulation() {
268      PrepareSimulation();
269      Scenario = new CardGameScenario();
270      ParameterizeInitialAction();
271      RegisterScenarioEventHandlers();
272    }
273
274    public override IDeepCloneable Clone(Cloner cloner) {
275      return new CardGameSimulation(this, cloner);
276    }
277
278    [StorableHook(HookType.AfterDeserialization)]
279    private void AfterDeserialization() {
280      RegisterScenarioEventHandlers();
281    }
282
283    private void PrepareSimulation() {
284      Activities = new Activity<CardGameModel>[] { new DealerActivity(), new PlayerActivity() };
285      Reporters = new Reporter<CardGameModel>[0];
286      InitialAction = new InitializeModelAction();
287      Model = new CardGameModel();
288    }
289
290    private void RegisterScenarioEventHandlers() {
291      Scenario.NumberOfPlayersParameter.Value.ValueChanged += ParametersChanged;
292      Scenario.InitialCoinsParameter.Value.ValueChanged += ParametersChanged;
293    }
294
295    protected override void OnProblemChanged() {
296      base.OnProblemChanged();
297      RegisterScenarioEventHandlers();
298      ParameterizeInitialAction();
299    }
300
301    protected override void OnPrepared() {
302      PrepareSimulation();
303      ParameterizeInitialAction();
304      base.OnPrepared();
305    }
306
307    private void ParametersChanged(object sender, EventArgs e) {
308      ParameterizeInitialAction();
309    }
310
311    private void ParameterizeInitialAction() {
312      ((InitializeModelAction)InitialAction).NumberOfPlayers = Scenario.NumberOfPlayersParameter.Value.Value;
313      ((InitializeModelAction)InitialAction).InitialCoins = Scenario.InitialCoinsParameter.Value.Value;
314    }
315  }
316
317  [Item("Card Game Scenario", "A card game scenario.")]
318  [StorableClass]
319  public sealed class CardGameScenario : Scenario {
320    public IFixedValueParameter<IntValue> NumberOfPlayersParameter {
321      get { return (IFixedValueParameter<IntValue>)Parameters["NumberOfPlayers"]; }
322    }
323    public IFixedValueParameter<IntValue> InitialCoinsParameter {
324      get { return (IFixedValueParameter<IntValue>)Parameters["InitialCoins"]; }
325    }
326
327    public CardGameScenario() {
328      Parameters.Add(new FixedValueParameter<IntValue>("NumberOfPlayers", "The number of players.", new IntValue(5)));
329      Parameters.Add(new FixedValueParameter<IntValue>("InitialCoins", "The number of initial coins.", new IntValue(3)));
330    }
331    [StorableConstructor]
332    private CardGameScenario(bool deserializing) : base(deserializing) { }
333    private CardGameScenario(CardGameScenario original, Cloner cloner)
334      : base(original, cloner) {
335    }
336    public override IDeepCloneable Clone(Cloner cloner) {
337      return new CardGameScenario(this, cloner);
338    }
339  }
340  #endregion
341
342  #region Activities
343  [StorableClass]
344  public sealed class DealerActivity : Activity<CardGameModel> {
345    public override IEnumerable<Type> MonitoredActions {
346      get { return new[] { typeof(InitializeModelAction), typeof(FinalizeRoundAction), typeof(FoldCardsAction), typeof(AcceptCardsAction) }; }
347    }
348
349    [StorableConstructor]
350    private DealerActivity(bool deserializing) : base(deserializing) { }
351    private DealerActivity(DealerActivity original, Cloner cloner) : base(original, cloner) { }
352    public DealerActivity() : base(new SortedListEventQueue<CardGameModel>()) { }
353
354    public override IDeepCloneable Clone(Cloner cloner) {
355      return new DealerActivity(this, cloner);
356    }
357
358    public override void ManageEvents(CardGameModel model, IAction<CardGameModel> lastAction) {
359      if (lastAction is InitializeModelAction || lastAction is FinalizeRoundAction) {
360        if (model.Players.Count <= 1) return;
361        var nextPlayer = model.Players[(model.DealerIndex + 1) % model.Players.Count];
362        EventQueue.Push(model.CurrentTime + 1, new DealCardsAction(nextPlayer, doYouKeep: true));
363      } else if (lastAction is AcceptCardsAction || lastAction is FoldCardsAction) {
364        EventQueue.Push(model.CurrentTime + 1, new DealCardsAction(model.Players.Where(x => x.Hand == null)));
365        EventQueue.Push(model.CurrentTime + 2, new DealTableCardsAction());
366      }
367    }
368  }
369
370  [StorableClass]
371  public sealed class PlayerActivity : Activity<CardGameModel> {
372    public override IEnumerable<Type> MonitoredActions {
373      get { return new[] { typeof(DealCardsAction), typeof(DealTableCardsAction), typeof(NextPlayerAction) }; }
374    }
375
376    [StorableConstructor]
377    private PlayerActivity(bool deserializing) : base(deserializing) { }
378    private PlayerActivity(PlayerActivity original, Cloner cloner) : base(original, cloner) { }
379    public PlayerActivity() : base(new SortedListEventQueue<CardGameModel>()) { }
380
381    public override IDeepCloneable Clone(Cloner cloner) {
382      return new PlayerActivity(this, cloner);
383    }
384
385    public override void ManageEvents(CardGameModel model, IAction<CardGameModel> lastAction) {
386      if (lastAction is DealCardsAction) ManageDealCards(model, (DealCardsAction)lastAction);
387      else if (lastAction is DealTableCardsAction) ManageTableCards(model);
388      else if (lastAction is NextPlayerAction) ManageNextPlayer(model);
389    }
390
391    private void ManageDealCards(CardGameModel model, DealCardsAction dealCardsAction) {
392      if (dealCardsAction.DoYouKeep) {
393        var player = dealCardsAction.Players[0];
394        if (player.Hand.Rating() < 16)
395          EventQueue.Push(model.CurrentTime + 1, new FoldCardsAction(player));
396        else EventQueue.Push(model.CurrentTime + 1, new AcceptCardsAction());
397      }
398    }
399
400    private void ManageTableCards(CardGameModel model) {
401      var player = model.Players[model.PlayerIndex];
402      if (player.Hand.Rating() > 20)
403        EventQueue.Push(model.CurrentTime + 1, new PlayerBlocksAction());
404      else
405        EventQueue.Push(model.CurrentTime + 1, new SwapCardsAction(player.Hand, model.Table, model.Random.Next(1, 4), model.Random.Next(1, 4)));
406      EventQueue.Push(model.CurrentTime + 2, new NextPlayerAction());
407    }
408
409    private void ManageNextPlayer(CardGameModel model) {
410      var player = model.Players[model.PlayerIndex];
411      if (player.Blocks || player.Hand.Rating() == 36) {
412        EventQueue.Push(model.CurrentTime + 1, new FinalizeRoundAction());
413        return;
414      }
415      if (player.Hand.Rating() > 20)
416        EventQueue.Push(model.CurrentTime + 1, new PlayerBlocksAction());
417      else
418        EventQueue.Push(model.CurrentTime + 1, new SwapCardsAction(player.Hand, model.Table, model.Random.Next(1, 4), model.Random.Next(1, 4)));
419      EventQueue.Push(model.CurrentTime + 2, new NextPlayerAction());
420    }
421  }
422  #endregion
423
424  #region Actions
425  [StorableClass]
426  public sealed class InitializeModelAction : Action<CardGameModel> {
427    [Storable]
428    public int NumberOfPlayers { get; set; }
429    [Storable]
430    public int InitialCoins { get; set; }
431
432    private InitializeModelAction(InitializeModelAction original, Cloner cloner)
433      : base(original, cloner) {
434      NumberOfPlayers = original.NumberOfPlayers;
435      InitialCoins = original.InitialCoins;
436    }
437    public InitializeModelAction() {
438      NumberOfPlayers = 5;
439      InitialCoins = 3;
440    }
441
442    public override void Execute(CardGameModel model) {
443      model.CurrentTime = 0;
444      model.Random = new FastRandom();
445      model.Players = new List<Player>();
446      for (int i = 0; i < NumberOfPlayers; i++) {
447        model.Players.Add(new Player("Player " + i, InitialCoins));
448      }
449      model.DealerIndex = model.Random.Next(model.Players.Count);
450      model.PlayerIndex = (model.DealerIndex + 1) % model.Players.Count;
451      model.Deck = new Deck();
452      model.Deck.Shuffle();
453      model.Table = null;
454      model.Talon = null;
455    }
456
457    public override IDeepCloneable Clone(Cloner cloner) {
458      return new InitializeModelAction(this, cloner);
459    }
460  }
461
462  [StorableClass]
463  public sealed class FinalizeRoundAction : Action<CardGameModel> {
464    private FinalizeRoundAction(FinalizeRoundAction original, Cloner cloner) : base(original, cloner) { }
465    public FinalizeRoundAction() { }
466
467    public override void Execute(CardGameModel model) {
468      var minScore = model.Players.Min(x => x.Hand.Rating());
469      foreach (var p in model.Players.Where(x => x.Hand.Rating() == minScore))
470        p.Coins--;
471
472      model.DealerIndex -= model.Players.Select((v, i) => new { Idx = i, Player = v }).Count(x => x.Idx <= model.DealerIndex && x.Player.Coins < 0);
473      model.Players = model.Players.Where(x => x.Coins >= 0).ToList();
474      foreach (var p in model.Players) {
475        p.Blocks = false;
476        p.Hand = null;
477      }
478      if (model.Players.Count > 0) {
479        model.DealerIndex = Math.Min(model.DealerIndex, model.Players.Count);
480        model.DealerIndex = (model.DealerIndex + 1) % model.Players.Count;
481        model.PlayerIndex = (model.DealerIndex + 1) % model.Players.Count;
482      }
483      model.Deck = new Deck();
484      model.Deck.Shuffle();
485      model.Table = null;
486      model.Talon = null;
487    }
488
489    public override IDeepCloneable Clone(Cloner cloner) {
490      return new FinalizeRoundAction(this, cloner);
491    }
492  }
493
494  [StorableClass]
495  public sealed class DealCardsAction : Action<CardGameModel> {
496    [Storable]
497    public Player[] Players { get; private set; }
498    [Storable]
499    public bool DoYouKeep { get; private set; }
500
501    private DealCardsAction(DealCardsAction original, Cloner cloner)
502      : base(original, cloner) {
503      Players = original.Players.Select(cloner.Clone).ToArray();
504      DoYouKeep = original.DoYouKeep;
505    }
506    private DealCardsAction() { }
507    public DealCardsAction(Player player, bool doYouKeep) {
508      Players = new[] { player };
509      DoYouKeep = doYouKeep;
510    }
511    public DealCardsAction(IEnumerable<Player> players, bool doYouKeep = false) {
512      Players = players.ToArray();
513      DoYouKeep = doYouKeep;
514    }
515
516    public override void Execute(CardGameModel model) {
517      foreach (var player in Players)
518        player.Hand = new Hand(model.Deck.Deal3());
519    }
520
521    public override IDeepCloneable Clone(Cloner cloner) {
522      return new DealCardsAction(this, cloner);
523    }
524  }
525
526  [StorableClass]
527  public sealed class FoldCardsAction : Action<CardGameModel> {
528    [Storable]
529    public Player Player { get; private set; }
530
531    private FoldCardsAction(FoldCardsAction original, Cloner cloner)
532      : base(original, cloner) {
533      Player = cloner.Clone(original.Player);
534    }
535    public FoldCardsAction(Player player) {
536      Player = player;
537    }
538
539    public override void Execute(CardGameModel model) {
540      model.Talon = Player.Hand;
541      Player.Hand = null;
542    }
543
544    public override IDeepCloneable Clone(Cloner cloner) {
545      return new FoldCardsAction(this, cloner);
546    }
547  }
548
549  [StorableClass]
550  public sealed class AcceptCardsAction : Action<CardGameModel> {
551    [StorableConstructor]
552    private AcceptCardsAction(AcceptCardsAction original, Cloner cloner) : base(original, cloner) { }
553    public AcceptCardsAction() { }
554
555    public override void Execute(CardGameModel model) { }
556
557    public override IDeepCloneable Clone(Cloner cloner) {
558      return new AcceptCardsAction(this, cloner);
559    }
560  }
561
562  [StorableClass]
563  public sealed class DealTableCardsAction : Action<CardGameModel> {
564    private DealTableCardsAction(DealTableCardsAction original, Cloner cloner) : base(original, cloner) { }
565    public DealTableCardsAction() { }
566
567    public override void Execute(CardGameModel model) {
568      model.Table = new Hand(model.Deck.Deal3());
569    }
570
571    public override IDeepCloneable Clone(Cloner cloner) {
572      return new DealTableCardsAction(this, cloner);
573    }
574  }
575
576  [StorableClass]
577  public sealed class SwapCardsAction : Action<CardGameModel> {
578    [Storable]
579    public Hand Source { get; private set; }
580    [Storable]
581    public Hand Target { get; private set; }
582    [Storable]
583    public int SourceIndex { get; private set; }
584    [Storable]
585    public int TargetIndex { get; private set; }
586
587    private SwapCardsAction(SwapCardsAction original, Cloner cloner)
588      : base(original, cloner) {
589      Source = cloner.Clone(original.Source);
590      Target = cloner.Clone(original.Target);
591      SourceIndex = original.SourceIndex;
592      TargetIndex = original.TargetIndex;
593    }
594    public SwapCardsAction(Hand source, Hand target, int sourceIndex, int targetIndex) {
595      Source = source;
596      Target = target;
597      SourceIndex = sourceIndex;
598      TargetIndex = targetIndex;
599    }
600
601    public override void Execute(CardGameModel model) {
602      if (SourceIndex == -1) {
603        var source = Source.Cards.ToArray();
604        Source.Cards = Target.Cards;
605        Target.Cards = source;
606      } else {
607        var sourceCards = Source.Cards.ToArray();
608        var targetCards = Target.Cards.ToArray();
609        var h = sourceCards[SourceIndex - 1];
610        sourceCards[SourceIndex - 1] = targetCards[TargetIndex - 1];
611        targetCards[TargetIndex - 1] = h;
612        Source.Cards = sourceCards;
613        Target.Cards = targetCards;
614      }
615    }
616
617    public override IDeepCloneable Clone(Cloner cloner) {
618      return new SwapCardsAction(this, cloner);
619    }
620  }
621
622  [StorableClass]
623  public sealed class PlayerBlocksAction : Action<CardGameModel> {
624    private PlayerBlocksAction(PlayerBlocksAction original, Cloner cloner) : base(original, cloner) { }
625    public PlayerBlocksAction() { }
626
627    public override void Execute(CardGameModel model) {
628      model.Players[model.PlayerIndex].Blocks = true;
629    }
630
631    public override IDeepCloneable Clone(Cloner cloner) {
632      return new PlayerBlocksAction(this, cloner);
633    }
634  }
635
636  [StorableClass]
637  public sealed class NextPlayerAction : Action<CardGameModel> {
638    private NextPlayerAction(NextPlayerAction original, Cloner cloner) : base(original, cloner) { }
639    public NextPlayerAction() { }
640
641    public override void Execute(CardGameModel model) {
642      model.PlayerIndex = (model.PlayerIndex + 1) % model.Players.Count;
643    }
644
645    public override IDeepCloneable Clone(Cloner cloner) {
646      return new NextPlayerAction(this, cloner);
647    }
648  }
649  #endregion
650}
Note: See TracBrowser for help on using the repository browser.