Repository: danielricci/solitaire
Branch: master
Commit: 8d46ac93471f
Files: 71
Total size: 458.8 KB
Directory structure:
gitextract_4kx7qtqt/
├── .classpath
├── .gitignore
├── .project
├── .settings/
│ ├── org.eclipse.core.resources.prefs
│ ├── org.eclipse.jdt.core.prefs
│ └── org.eclipse.jdt.ui.prefs
├── LICENSE
├── README.md
├── assets/
│ ├── blogs/
│ │ ├── Autocomplete/
│ │ │ └── AutoComplete.txt
│ │ ├── Bug Fixes 1/
│ │ │ └── blogpost.txt
│ │ ├── Deck Selection Images/
│ │ │ ├── 2018-11-10 16_41_10-Microsoft Edge.greenshot
│ │ │ └── Features.txt
│ │ ├── Draw Three/
│ │ │ └── entry.txt
│ │ ├── Release Date/
│ │ │ └── blog.txt
│ │ ├── Undo/
│ │ │ └── Undo.txt
│ │ └── Win Animation/
│ │ └── WinAnimation.txt
│ ├── editor/
│ │ ├── solitaire-data/
│ │ │ ├── tilemap.jar
│ │ │ └── tilemap.xml
│ │ └── solitaire.xml
│ ├── images/
│ │ └── decks/
│ │ └── gimp_projects/
│ │ ├── deck_10.xcf
│ │ ├── deck_11.xcf
│ │ ├── deck_12.xcf
│ │ └── deck_7.xcf
│ └── initial requirements.txt
├── data/
│ └── generated/
│ ├── tilemap.jar
│ └── tilemap.xml
├── libs/
│ └── mead.jar
├── properties/
│ └── resources/
│ ├── Localization.csv
│ └── LocalizationStrings.java
└── src/
└── game/
├── application/
│ └── Game.java
├── config/
│ └── OptionsPreferences.java
├── controllers/
│ ├── CardController.java
│ └── MovementRecorderController.java
├── entities/
│ ├── AbstractCardEntity.java
│ ├── BacksideCardEntity.java
│ ├── ClubCardEntity.java
│ ├── DiamondCardEntity.java
│ ├── FoundationCardEntity.java
│ ├── HeartCardEntity.java
│ ├── NullCardEntity.java
│ ├── SpadeCardEntity.java
│ └── StockCardEntity.java
├── menu/
│ ├── AboutMenuItem.java
│ ├── DeckMenuItem.java
│ ├── ExitMenuItem.java
│ ├── GitHubMenuItem.java
│ ├── NewGameMenuItem.java
│ ├── OnTopMenuItem.java
│ ├── OptionsMenuItem.java
│ └── UndoMenuItem.java
├── models/
│ ├── CardModel.java
│ └── MovementModel.java
└── views/
├── AbstractPileView.java
├── CardOutlineView.java
├── CardView.java
├── DeckSelectionDialogView.java
├── FoundationPileView.java
├── GameView.java
├── IUndoable.java
├── OptionsDialogView.java
├── ScoreView.java
├── StatusBarView.java
├── StockView.java
├── TableauPileView.java
├── TalonPileView.java
├── TimerView.java
├── VegasScoreView.java
├── components/
│ └── ExclusiveLineBorder.java
└── helpers/
├── DeckAnimationHelper.java
├── ViewHelper.java
└── WinAnimationHelper.java
================================================
FILE CONTENTS
================================================
================================================
FILE: .classpath
================================================
================================================
FILE: .gitignore
================================================
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.war
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
/bin/
.DS_Store
================================================
FILE: .project
================================================
Solitaire
org.eclipse.jdt.core.javabuilder
org.eclipse.jdt.core.javanature
================================================
FILE: .settings/org.eclipse.core.resources.prefs
================================================
eclipse.preferences.version=1
encoding/=UTF-8
================================================
FILE: .settings/org.eclipse.jdt.core.prefs
================================================
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.problem.APILeak=warning
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.comparingIdentical=error
org.eclipse.jdt.core.compiler.problem.deadCode=warning
org.eclipse.jdt.core.compiler.problem.deprecation=warning
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.problem.nullReference=warning
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
================================================
FILE: .settings/org.eclipse.jdt.ui.prefs
================================================
eclipse.preferences.version=1
org.eclipse.jdt.ui.ignorelowercasenames=true
org.eclipse.jdt.ui.importorder=java;javax;org;com;framework;game;
org.eclipse.jdt.ui.javadoc=true
org.eclipse.jdt.ui.ondemandthreshold=99
org.eclipse.jdt.ui.staticondemandthreshold=99
org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n *//**\n * @param ${param} the ${bare_field_name} to set\n *//**\n * Constructs a new instance of this class type\n *\n * ${tags}\n *//**\n* Daniel Ricci <thedanny09@icloud.com>\n*\n* Permission is hereby granted, free of charge, to any person\n* obtaining a copy of this software and associated documentation\n* files (the "Software"), to deal in the Software without restriction,\n* including without limitation the rights to use, copy, modify, merge,\n* publish, distribute, sublicense, and/or sell copies of the Software,\n* and to permit persons to whom the Software is furnished to do so, subject\n* to the following conditions\:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n*\n* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n* IN THE SOFTWARE.\n*/\n/**\n * @author Daniel Ricci <thedanny09@icloud.com>\n *//**\n * \n *//**\n *\n *\n * ${tags}\n *//**\n * ${tags}\n * ${see_to_target}\n */${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();// ${todo} Auto-generated method stub\n${body_statement}${body_statement}\n// ${todo} Auto-generated constructor stubreturn ${field};${field} \= ${param};
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024 Daniel Ricci
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Solitaire
This game serves as a loving homage to the classic Solitaire experience originally found in Microsoft Windows 95. It faithfully preserves the essence and charm of the original game, with only minor bug fixes applied to ensure a seamless experience. My goal is to showcase and honor the timeless appeal of vanilla Solitaire for Windows 95, allowing players to relive those nostalgic moments with authenticity.

# Instructions
To run the game, make sure that you have Java 1.8 installed on your machine. If double-clicking on the .jar file doesn't work, you can also run the game through the command-line using java -jar solitaire.jar
================================================
FILE: assets/blogs/Autocomplete/AutoComplete.txt
================================================
In my last post, I decided to clean house and close a bunch of bugs that have been hanging around my backlog. Some of these bug fixes had to do with issues related to the `Undo` feature that I had implemented a while back. I also solved bugs related to the rendering of a card after performing certain operations, and issues related to toggling the `Outline` feature on/off. There was also a small fix that I did on the UI side so that vertical spacing between cards on each of the Tableau views would be consistent with that of the original game.
The feature that I will present to you today is the `Autocomplete` feature. This feature builds off the Automove feature where you double click on a card, and on the second mouse down of that card, an attempt will be performed to move that card to a valid position on one of the Foundation views, with priority given to the left-most Foundation position. I also closed up a bunch of bugs from the backlog as well, along with some bugs that were introduced because of this feature that was implemented.
The Autocomplete feature takes this a step further by automating this feature with respect to a certain set of cards on the board. The idea with being able to Autocomplete is that the user performs a right-click action anywhere on the board, including on any card available on the Tableau, or the Stock, or any card on the Talon pile.
When this occurs, all top-most face-up cards on the Talon and Tableau piles will be auto-moved (if the move is valid) towards the Foundation view. This is a simple way to be able to move all your cards to the Foundation piles without having to drag the card over it or double click each individual card.
Here is what the code looks like.
/**
* Performs an auto complete based on all available cards
*/
private static void performCardsAutocomplete() {
ViewFactory viewFactory = AbstractFactory.getFactory(ViewFactory.class);
List cards = new ArrayList();
// Get the top-most talon card
CardView talonCard = viewFactory.get(TalonPileView.class).getLastCard();
if(talonCard != null) {
cards.add(talonCard);
}
// Get all available top-most front-facing cards
List tableauPileViews = viewFactory.getAll(TableauPileView.class);
Collections.reverse(tableauPileViews);
for(TableauPileView view : tableauPileViews) {
CardView card = view.getLastCard();
if(card != null && !card.isBacksideShowing()) {
cards.add(card);
}
}
// Go through the list and apply the automove on each card until there are no cards
// left or all cards have been iterated over
while(cards.size() > 0) {
boolean keepGoing = false;
for(int i = 0; i < cards.size(); ++i) {
if(cards.get(i).performCardAutoMovement()) {
cards.remove(i);
keepGoing = true;
break;
}
}
if(!keepGoing) {
break;
}
}
// If the talon card was moved then enabled the top-most card so that the next card can be played
// TODO Can this be self-contained??
if(talonCard != null && !cards.contains(talonCard)) {
CardView lastTalonCard = viewFactory.get(TalonPileView.class).getLastCard();
if(lastTalonCard != null) {
lastTalonCard.setEnabled(true);
}
}
}
Based on the code above, this is how the functionality works. The first step is to gather the cards that will be operated on. So we create a list and populate it with the current card that is on the Talon (if any). After that, I go through each Foundation pile (starting at the left-most pile), and I try to get the top-most (card all the way at the bottom) if and only if the card has it's front face showing. So, the priority for these cards is Talon pile first followed by Tableau piles second starting with the left-most pile.
The next step is to go through this left, and for each card attempt to perform an Automove to the foundation. If a card can be Automoved, then I remove that card from the list and start the process all over again until I reach the end, or I have no more cards left in my list.
Finally, I handle an edge case (Bug #142 shown below) that ensures that if the card from the Talon was moved, I initiate a method call to enable the next card. Ideally, this should be self-contained within the Talon, so I put a TODO within the comments and hopefully when I go through and refactor the code or solve a bug / implement a feature related to this I can include the change in that changeset
Here is an example of the Autocomplete feature in action
<>
Apart from the Autocomplete feature, I solved a bunch of bugs that I present to you below.
1. After performing an automove, the talon card is not enabled
https://github.com/danielricci/solitaire/issues/142
2. Going from outline to non-outline with draw three lets you choose any of the three cards and not the right-most one only
https://github.com/danielricci/solitaire/issues/141
3. Dragging a foundation card will think it collided with itself, preventing an undo from happening
https://github.com/danielricci/solitaire/issues/145
4. Performing an undo when an ace goes to the foundation from the tableau does not always work
https://github.com/danielricci/solitaire/issues/135
5. When playing with a timer on, a dragged card still resets to the stock position
https://github.com/danielricci/solitaire/issues/147
6. Going from outline to non-outline doesn't let you move any tableau cards
https://github.com/danielricci/solitaire/issues/150
7. (Draw Three) - Going from outline to non-outline and then dragging a card and letting go puts it in the wrong position
https://github.com/danielricci/solitaire/issues/146
8. Opening the deck dialog and clicking OK will populate the wrong selected item
https://github.com/danielricci/solitaire/issues/140
The next feature that I am going to work on is the win animation screen. You all know (should know) what this looks like, and I have been waiting a while to get this one done. There a couple of known bugs that need to be fixed as well, along with a couple of UI tweaks and a nice code cleanup pass. There are some translations that I need to fill in as well, and I have a few days of solid testing for both Mac and PC before stamping the game as done.
You can always follow my progress by following the game located at https://github.com/danielricci/solitaire and if you have any questions I will do my best to answer them.
Take care, until my next blog post.
================================================
FILE: assets/blogs/Bug Fixes 1/blogpost.txt
================================================
In my last post, I talked about the `Undo` feature that was implemented. Undo allows you to revert the last valid move that you made and will put back the score that you had then, with a hit of two points. I demonstrated all the different ways that you can undo your move, basically all the different possible moves that constitute a valid `Undo` move.
Furthermore, I introduced a bunch of bug fixes related to the game. I did not demonstrate how the bug fixes are now working, however future blog posts that include bug fixes will not be accompanied by a small demonstration of the expected result.
I had scheduled some time for me to work on the `Autocomplete` feature. For this feature, the user will be able to right click anywhere on the board, and all the cards that are face up that can be moved to any of the foundations will be moved there. I don't see this feature taking a lot of time to implement since being able to move a card to the foundation has already been implemented, such as when the user double-clicks on a card.
As I was preparing myself to start work on this feature, I glanced over at the number of bugs that I logged, and I felt that it would be a nice gesture to get a few of the bugs out the door before I introduce a new feature that would undoubtedly introduce a few bugs here and there.
Therefore, I took a step back and got to work on a bunch of bugs, which I will present to you below.
https://github.com/danielricci/solitaire/issues/125
1. Clicking on the stock and then performing an undo, does not subtract 2
https://github.com/danielricci/solitaire/issues/126
2. Double-clicking on an Ace will position itself momentarily at 0,0 if you hold the mouse down on the second mouse down
https://github.com/danielricci/solitaire/issues/107
3. Switching from outline to non-outline does not update the cards in the talon view
https://github.com/danielricci/solitaire/issues/98
4. When dragging multiple cards in outline mode, the outline does not look consistent
https://github.com/danielricci/solitaire/issues/128
5. In outline mode, doubling to automove a card shows a small green artifact on the status bar
https://github.com/danielricci/solitaire/issues/131
6. Talon Card Proportions
https://github.com/danielricci/solitaire/issues/129
7. Automove shows the card at the top left on the second mouse down
https://github.com/danielricci/solitaire/issues/133
8. Dragging more than one card causes the cards to be cut at the bottom when playing in a timed game
https://github.com/danielricci/solitaire/issues/134
9. Playing a timed game causes the card to jump to the origin every tick
Now that I closed a lot of bugs, It's time to implement another feature. I will continue my work on the Autocomplete item.
You can always follow my progress by following the game located at https://github.com/danielricci/solitaire and if you have any questions I will do my best to answer them.
Take care, until my next blog post.
================================================
FILE: assets/blogs/Deck Selection Images/Features.txt
================================================
================================================
FILE: assets/blogs/Draw Three/entry.txt
================================================
In my last post, I implemented the `Card Back` deck selection feature, which allows you to choose between 12 built-in deck images that would change the card backs of all the cards in the game. You can view the blog post <<>>.
The next feature that I implemented was the ability to play Solitaire using the `Draw Three` game mode, which allows you to play the game using three cards at a time. I also added the functionality for scoring when in this mode for both `Vegas` and `Standard`.
Here is what the `Draw Three` game mode looks like, and how to set the game for this mode.
From the `File` menu, choose `Options`. When the dialog open, click on the `Draw Three` radio button, and then click on `OK`.
You will notice that your game will restart, and you will now have three cards drawn at a time when you click on your Stock card.
This feature took quite a while for me to implement as there were a lot of small things that I had to change. Here is the list of features/functionalities that I had to work on to get my game to support the `Draw Three` game mode.
1. When clicking on the Stock card, three cards should be loaded at a time instead of one.
This was simply a matter of taking the `blank card` that I use to partition my deck when playing in `Draw One`, and have it skip three cards at a time instead of one. At this point, all three cards will be stacked on top of each other, however, my next task would fix that up even further by applying an offset to each card.
2. When three cards are shown, they should be rendered next to each other.
This task was done to compliment my first task. When loading three cards at a time, all that I needed to do was iterate through each card, and multiply the x-axis location of the card by (iteration * 12px).
Because of the GridBagLayout that I use to render the different piles in the game, I had some trouble with this at first. mostly with the view chopping off the two right-most cards because the view was initially only ever sized for rendering at most one card.
I tried to take row 0, columns 1 and column 2, and merge them both together using a `colspan` in one of my GridBagConstraints, however this would cause some alignment problems, so I instead played with the insets and padding of the view, so that three cards could be rendered without any of the cards being chopped.
Here is some of the code that I wrote to achieve this.
3. When three cards are loaded, the right-most card should be the only card that can be interacted with.
This was a little bit tricky because I didn't want to introduce a flag and then have to manage that depending on the different states of the game. Experience has taught me just how difficult it can be to manage the many different states of an object, and the more flags that you add to a class, the more things can go wrong.
Therefore, I overwrote the setEnabled method of my view, and I would instead toggle mouse events associated with that view. I had to do this for both the CardView and the CardOutline since they both do not have a common class that they inherit from as of yet, apart from the generic PanelView class that is Engine specific and I didn't want to put the game-specific code in the engine.
4. When a single card from the three card pile moves to the Tableau or the Foundation pile, the next right-most card should now be playable.
This involved two sub-tasks that I needed to introduce into the game. The first was to introduce a `mouseReleased` event on a card within the Talon deck, such that when the event would occur, I would verify if the card that was just dragged still exists in the Talon.
If the card existed then the move was invalid, so do nothing with the other cards. If the card was moved then take the right-most card and enable it. If there are no more cards left, show the at-most-three cards played and enable the right-most card there.
5. When playing using the `Vegas` scoring style, you should only be able to go through the entire deck at most three times
This code was actually already in place, however, there is currently no art to show that the deck can no longer be recycled. Here is the task for adding the required art to the Stock view.
https://github.com/danielricci/solitaire/issues/39
6. When playing using the `Draw Three` game mode using the `Standard` scoring style, 20 points should be taken off of the score whenever a deck has been recycled.
This was very straight-forward, here is the code for doing this.
7. When playing using the `Draw Three` game mode, showing three cards should hide the currently shown cards.
By default, all cards are now marked as not having any visibility. When the three cards come into play, their visibility is shown, and when the card deck re-cycles, their visibility is set back to hidden. When the next set of three cards is shown, the previous at-most-three cards have their visibility set to hidden.
8. When playing using the `Draw Three` game mode, if you move the three currently shown cards, the previously shown cards should now be made visible to you.
This involves looking for my hidden `Blank Card` that keeps tabs of where I am in the current deck. When all three cards have been dragged to one of the other piles (Foundation or Tableau) in the game, I use the blank card index to look for the three cards with a layer of +1,+2, and +3 to that of the blank card. Obviously, this will go up to at most three cards, to avoid any `IndexOutOfBoundsException` issues from being occurring.
Here is the code related to that bit.
Here are the other issues that I worked on. I have linked the bug issues that I logged, in there I have included screenshots of the actual issue, and you can also go and take a look at the changeset that solves that issue.
1. You can now press on the `Enter` key when in the Options dialog instead of clicking on the `OK` button
Issue
Changeset
2. I fixed a bug where when you would play using the `Draw Three` game mode using the `Outline` option, you could draw all three cards` outline instead of just the right-most card.
Issue
Changeset
3. I fixed a bug where when you were using the `Outline` option, after performing a double-click on a card, if you were to hold the mouse down on the second click and keep it held, the outline would still be visible on the card. This was more of a rendering artifact, however, I still wanted to correct it.
Issue
Changeset
4. I fixed a bug where while playing using the `Draw Three` game mode, if you were to move one card and then play through the entire deck, the next time that you would play through the deck, one of the cards` visibility property would not properly reset, causing the cards currently shown to be offset improperly
Issue
Changeset
5. Added some enhanced logging to better debug the `Draw-Three` game mode.
I linked the changeset under the main task for the `Draw Three` game mode, however, I will give an explanation below on how this all works and what the symbols actually mean.
https://github.com/danielricci/solitaire/issues/12
================================================
FILE: assets/blogs/Release Date/blog.txt
================================================
In my last post, I presented the win animation feature that I finished working on. This is the animation that happens when you win the game, similar to the one shown from the Win95 version of the game. This post is to update everyone on the hard release date that I have set for myself and to present what I have been doing for the past week.
The release date for the game will be March 15th, 2019.
Here is what I have left to do.
I have a few UI tweaks that I need to make.
I'm working on a cascading effect for the card when you play in `Draw Three` mode.
Draw three proportions don't properly line up on the third pass - https://github.com/danielricci/solitaire/issues/167
There are currently no new bugs that have been found as of yet, so that's good news going into the week-long stretch of testing. My testing will be done for both MacOS and Windows, so I'm going to come up with a quick test plan to run through.
I want to make sure that there is Javadoc and proper comments on all the code in my code base, which I have done a very good job at keeping that up to date however its always good to do a quick pass.
I need to build a version of the framework that I am using, and then build the 1.0 of the game and make sure that it runs well.
Here is what I have done in the past week.
I spent time working on various bug fixes and updating the game data to support transparent corners since until now the cards have just been squares with their corners rendered through. I also worked on and am currently finishing off the cascading effect for the Talon pile view
Stock Card Proportions
https://github.com/danielricci/solitaire/issues/132
Just like in the original game, when you click the stock button, the cards will display a cascading effect that will `count down` as you go through the deck.
Talon Card Proportions
https://github.com/danielricci/solitaire/issues/130
As shown above, notice how the cards cascade over each-other the more you go through the deck
There will be something similar done for Draw Three that I am working on right now.
You can always follow my progress by following the game located at https://github.com/danielricci/solitaire, and if you have any questions I will do my best to answer them.
Take care, until my next blog post.
================================================
FILE: assets/blogs/Undo/Undo.txt
================================================
In my last post, I introduced some new art assets into the game related to the stock card, and I added a visual effect for when you hover a card over a foundation when playing with the `outline` option checked on.
The game is definitely starting to look a lot more like the original Solitaire. However, there is still some polish needed on the UI, and of course, some missing features such as the right-click `autoplay` feature, and the grand finale feature for when the player wins the game. I'm so looking forward to that feature.
For the past 3-4 weeks, apart from some architectural adjustments to the code base, I worked on the undo feature. The undo feature lets you undo the last move that was played in the game, and of course, the score that was associated with the move. Performing an undo also has a cost associated with it, so your score will take a small hit of two points.
Originally, I had overlooked how the `Draw Three` game mode worked behind the scenes. Initially, each card was on it's own layer similar to how the `Draw One` game mode works, however, this implementation caused a bunch of edge case issues that I was not willing to hack, so I took a step back and I re-designed the Draw Three feature from the ground up to work properly this time.
Furthermore, whenever you click on the stock, I did not realize that the three cards currently shown would then be positioned in an overlapping fashion, I thought and had implemented it so that those cards would be hidden, and when you had no more cards left to drag, you would show the three cards `behind` your hand. This implementation was wrong, so I corrected it.
This correction required me to associate each set of three cards with their own unique layer and position. Since JLayeredPane natively supports these two concepts, it wasn't as difficult to adapt my code to work like this. A few areas of the code that are difficult to do are things like knowing which position within the layer the card is in so that you can apply the correct x-axis offset. Being able to distinguish between the last card in a set of three cards being dragged out, and knowing that if there is no collision, that it needs to be played back on its own layer, not on the layer `now` being shown.
Suffice to say, the undo now works very well, with a few bugs encountered here and there that have been logged.
Here is the list of possible undoable actions.
1. Clicking on the stock can now be undone. This will revert the cards being shown to the previous set of cards, depending on the game mode (Draw One vs. Draw Three).
2. Moving a card from the Talon can now be properly undone.
3. Moving a card from the Foundation can now be properly undone.
4. Moving a card from the tableau can now be properly undone.
5. Performing an undo will also remove the score that was associated with that move. Furthermore, performing an Undo will result in a loss of two points.
6. You can only undo your last valid move.
7. Clicking on a card that has its back shown cannot be undone, so you will lose the ability to undo for the previous move prior to that.
Apart from the undo feature, there were some lingering bugs that I wanted to solve. Here is the list of bugs that were fixed.
1. Fixed an issue related to the deck images being imported into a low-res fashion.
2. Fixed an issue where the foundation view would highlight when there was already a card on that foundation, and another card was being dragged over it.
3. Fixed an issue where dragging a card from the talon to the foundation would cause the dragged card to not be shown properly
4. Fixed an issue where dragging a king along with other cards onto an empty pile would cause the cards to not be properly displayed.
5. Fixed a bug where restarting the stock deck would cause previous cards to still be shown and not properly aligned when playing in `Draw Three` game mode.
6. Fixed an issue where dragging a card from the talon in `Draw Three` would not position the card back in the right place on the talon if there was no valid collision detected.
The next feature that I am implementing is the `auto-complete` feature where you right click on the board and all available valid moves are automatically performed for you. Once this is done
I will work on churning out all the bugs and the remaining UI changes that have to be done.
Once the game is stable, I will implement the final task, the `game win` animation that we all absolutely loved to watch when we were kids (and still today) when the game was won.
You can always follow my progress by following the game located at https://github.com/danielricci/solitaire, and if you have any questions I will do my best to answer them.
Take care, until my next blog post.
================================================
FILE: assets/blogs/Win Animation/WinAnimation.txt
================================================
In my last post, I presented the Autocomplete feature that I implemented. This feature lets you right-click anywhere on the board (including cards), and all face-up top-most cards will attempt to put themselves in one of the four Foundation piles above the board. I also mentioned a handful of bug fixes that were done.
The last feature that I will demonstrate here is the `Win Animation` feature that occurs when you win the game or push the Alt+Shift+2 cheat code to win.
The `Win Animation` is an animation that is played when you win the game, that is when all the cards are properly situated on each of the four Foundation piles above the board. The animation starts at the left-most pile, ending at the rightmost pile for each group of cards (Kings first, then Queen, Jack, 10, etc).
The path that the card follows is similar to a sine/cosine wave. Each card has a wave randomly generated that fits within a specified period. These choices are limited to a set number of choices based on my observations of the original game, and there are quite a few randomly generated choices that can occur.
I originally started prototyping this functionality using vanilla trigonometry, however, I found an implementation online that I felt was much simpler to implement, and that had much better readability for those not so fluent in the way of mathematical formulas. The implementation that I retrofitted into my game is the one from `Mr. Doob`, you can find the link to his code here.
Here is what the win animation looks like
There are a few parts to the code to make this work, and I will explain the major players of this algorithm in snippets below.
For this functionality, I create a class called `WinAnimationHelper` to manage the animation process. This class has a static method called `processCards` that gets called when someone wants to process all the cards on the Foundation piles.
/**
* Process all the cards held by the foundation views
*/
public static void processCards() {
// Get the list of foundation piles
List foundationsList = AbstractFactory.getFactory(ViewFactory.class).getAll(FoundationPileView.class);
// Reverse the list so that we start with the left-most pile.
Collections.reverse(foundationsList);
// Initialize this helper class
initialize();
// Populate the queue of items to be processed
_foundations.addAll(foundationsList);
}
Of interest is the `initialize()` method that I call above. This is a static method that creates a timer that processes a field called _foundations at a rate of 80 times per second. After I perform a call to add the available foundations to the queue, this timer will start to process them in a queue like fashion.
For each foundation pile that it processes, it will grab the top-most card and create a WinAnimationHelper object, passing in the card that it received. Here is what the constructor of this class looks like/
/**
* The change in `x` over time
*/
private double _deltaX = Math.floor(Math.random() * 6 - 3) * 2;
/*
* The change in `y` over time
*/
private double _deltaY = -Math.random() * 16;
/**
* Constructs a new instance of this class type
*
* @param cardView The card view to animate
*/
private WinAnimationHelper(CardView cardView) {
_cardView = cardView;
Point position = cardView.getParentIView().getContainerClass().getLocation();
_x = position.getX();
_y = position.getY();
if(_deltaX == 0) {
_deltaX = 1;
}
}
The above code will first initialize the deltas for this card. For the rate of change in on the X-Axis, the domain of available values are from [-6, 4]. For the rate of change on the Y-Axis, the range of available values are from (-16.0, 0]. Then the class object is initialized, where I store a reference to the passed in card and I populate the other fields such as the initial position. I also handle an edge case where if the change in X is 0, I set it to 1 so that there is at least some movement along the X-Axis.
After the object is constructed, my timer that created the object calls the method `update()` for each tick until the update can no longer occur because the card is out of bounds of the canvas dimensions. Here is what the method looks like.
/**
* Performs an update by performing both a next step point calculation and a draw routine
*
* @return TRUE if the operation was successful, false otherwise
*/
private boolean update() {
Point point = calculateNextStep();
if(point == null) {
return false;
}
draw(point);
return true;
}
The above code is very straight forward, calculate the next location of the card, and then draw to that point.
Here is the `calculateNextStep` method.
/**
* Calculates the next position that the currently set card will be at
*
* @return The position associated with the next step where the card would be at
*/
private Point calculateNextStep() {
// Take the change in X and the change in Y and apply them respectively
_x += _deltaX;
_y += _deltaY;
// If you are outside the left or right canvas limits then the card should not
// longer be positioned anywhere relevant so do not return any position
if(_x < -CardView.CARD_WIDTH || _x > _canvasWidth) {
return null;
}
// If the position is outside canvas height (with respect to the bottom of the card)
if(_y > _canvasHeight - CardView.CARD_HEIGHT) {
// Normalize the position of the card by placing it on the theoretical bottom of the canvas
_y = _canvasHeight - CardView.CARD_HEIGHT;
// Take the change in `y` inverse it, this along will cause the card to bounce upwards
// Take only a small percentage of the delta so that it bounces less
_deltaY = -_deltaY * 0.85;
}
_deltaY += 0.98;
return new Point((int)_x, (int)_y);
}
The above code takes the currently computed deltas and adds them to the current x and y positions that were recorded by the card. If the `x` location is outside the bounds of the canvas then there is no more computation to be performed, this is our exit case. If this is not the case then I check to see if the `y` position is outside the lower bounds of the canvas. If it is, I position the card the absolute bottom of the canvas and then I apply an inverse linear force to the current deltaY. This will cause the card to move upwards however only upwards by a certain percentage. This is an ever decreasing number that will simulate a `bounce` and that will eventually flatline itself with the y-axis if this statement is executed many times. Finally, I update the deltaY with a constant to ensure that the change in `y` counteracts the `bounce` effect.
Once this is computed, I perform a draw. Here is what the draw call looks like.
/**
* Draws the currently set card view to the specified position
*
* @param point The position to draw to
*/
private void draw(Point point) {
CardView cardView = CardView.createLightWeightCard(_cardView);
cardView.render();
ViewFactory viewFactory = AbstractFactory.getFactory(ViewFactory.class);
GameView gameView = viewFactory.get(GameView.class);
gameView.add(cardView, gameView.getComponentZOrder(viewFactory.get(StatusBarView.class)));
cardView.setBounds(new Rectangle(point.x, point.y, _cardView.getWidth(), _cardView.getHeight()));
}
This was tough because I really wanted to simply change the position of a single card and reuse the same buffer for performing the draw call. Doing this however was not possible because the layout manager that I am using is a Swing manager called GridBagLayout, and it was showing many artifacts that were making this functionality look horrible.
So instead for each point that I calculate I create a lightweight representation of the specified card view which is in layman terms a JPanel with an image. I take this card and I update it to the position that I calculated previously. I also make sure that it is added to the GameView and that it is position in the proper z-order.
The finishing touches for this feature were to make sure that clicking anywhere during the animation or pressing on any key stops the animation and asks you if you want to play again, this is in line with the original game. I also noticed on the original game that when I move the mouse during the animation that the animation animates faster. I don't like this at all, so I made sure not to do that as it detracts from the awesomeness of the animation itself.
So that is how I implemented the win animation.
I also fixed a few more bugs, that I will outline below
1. performing an undo doesn't undo the score, it just subtracts 2
https://github.com/danielricci/solitaire/issues/164
2. Performing an automove no longer updates the score
https://github.com/danielricci/solitaire/issues/155
3. Cannot perform an undo after doing an Autocomplete
https://github.com/danielricci/solitaire/issues/153
The next thing on my list is to normalize the UI. There are a few things that I noticed that the original game does that I have to implement. And of course, I need to validate that the UI as a whole looks/behaves like the original game as much as possible.
Take care, until my next blog post.
================================================
FILE: assets/editor/solitaire-data/tilemap.xml
================================================
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="e984a82d-092a-454f-984e-e38606a7f7e5" name="c1" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="0" y1="0" x2="71" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0adc2583-1a94-48fd-9b6e-b1a141bb76e2" name="c2" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="71" y1="0" x2="142" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="2373e07c-6a5d-404a-bf07-14b2211653f1" name="c3" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="142" y1="0" x2="213" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="92298418-a59d-44df-89c9-7e0c028131f2" name="c4" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="213" y1="0" x2="284" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="76153ba2-26c0-4c1e-8ba4-2b558f948138" name="c5" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="284" y1="0" x2="355" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="dfe50140-8e9c-41cb-b21d-c0af51b78436" name="c6" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="355" y1="0" x2="426" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="fb2435a2-6492-439b-8d34-03e8229a3b8e" name="c7" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="426" y1="0" x2="497" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0a0ecf52-7060-42f7-9d9d-72ffe26c8907" name="c8" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="497" y1="0" x2="568" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="7a84e376-6c0d-4ba6-a27f-adcf2a41476f" name="c9" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="568" y1="0" x2="639" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="f690e1b0-3c1b-4fd4-8754-09d5823bc76a" name="c10" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="639" y1="0" x2="710" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="ab0658dd-6958-4503-85ae-380dd54ec310" name="c11" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="710" y1="0" x2="781" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="58d2569a-ebd9-4049-b662-7c2dd68968c9" name="c12" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="781" y1="0" x2="852" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="9a666fae-7f67-4afa-8c4d-142d62c782d2" name="c13" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="852" y1="0" x2="923" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="d59b8ca9-5fa0-4f46-b7ac-93869a30117a" name="d1" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="0" y1="96" x2="71" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="9ec05b58-89fb-4689-b800-175cc034fab1" name="d2" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="71" y1="96" x2="142" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="d972f5c7-687b-4f65-aa17-f297ba99bc0e" name="d3" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="142" y1="96" x2="213" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="4f89e5ae-3554-4465-a6df-c409b5030fa2" name="d4" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="213" y1="96" x2="284" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0b1e1a44-f5c7-4fc7-b1b3-1ea0fbfae73a" name="d5" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="284" y1="96" x2="355" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a8f33327-dfbd-4989-805d-88847490ff20" name="d6" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="355" y1="96" x2="426" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="3ab272fa-f8cd-4c54-8c75-16ce3b5361c9" name="d7" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="426" y1="96" x2="497" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="61c0327c-0e96-4893-8dc1-4692e046bb6b" name="d8" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="497" y1="96" x2="568" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="44672b8c-c9c1-434d-803a-fca1b30ec92b" name="d9" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="568" y1="96" x2="639" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="29e7d023-9cf2-4776-beef-5f390a6fbc59" name="d10" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="639" y1="96" x2="710" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a582dd25-9a24-46c2-ba98-1927e3e55ac5" name="d11" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="710" y1="96" x2="781" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="ad1b28a4-61a9-4451-9759-9565cd7b0626" name="d12" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="781" y1="96" x2="852" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="6e1a2bea-c6fd-4ec0-a800-8d65fd038674" name="d13" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="852" y1="96" x2="923" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="2cc80226-f795-45e4-8c85-4f9acf7a1fb1" name="h1" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="0" y1="192" x2="71" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="879ae6f3-950e-44a0-82c5-4752e957c1b7" name="h2" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="71" y1="192" x2="142" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="d110f5d3-a456-4a14-a48a-33d03703e205" name="h3" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="142" y1="192" x2="213" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="956d7658-3fc6-4422-84ae-2c0027e416e2" name="h4" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="213" y1="192" x2="284" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="1be5f51f-7a2a-4d0b-8100-ad3343f796f5" name="h5" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="284" y1="192" x2="355" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="1fbbdf52-331a-47f7-8d1e-e84398cefda8" name="h6" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="355" y1="192" x2="426" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="53fe97cb-710f-4630-9c1c-8cdc03c77212" name="h7" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="426" y1="192" x2="497" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="298a41cf-2be4-422a-9b59-f1aadc41f48e" name="h8" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="497" y1="192" x2="568" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="db19c7f8-44d0-4e03-a848-747a3a31da9c" name="h9" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="568" y1="192" x2="639" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="934f6d8b-bbbe-43b1-a28d-913c5c80fef9" name="h10" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="639" y1="192" x2="710" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="7c588781-b5d9-493c-86dc-6b383cc33098" name="h11" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="710" y1="192" x2="781" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0d75364f-a431-41dc-9a23-75b0e200ffce" name="h12" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="781" y1="192" x2="852" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a3397bc9-187c-461a-b336-7059f6b8eadc" name="h13" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="852" y1="192" x2="923" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a4e9de58-b1c8-4a46-8b60-71c4c6a463c9" name="s1" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="0" y1="288" x2="71" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0db5fd69-a18f-4024-8a6b-b33b1514ebac" name="s2" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="71" y1="288" x2="142" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="7abd8566-d1ef-40a6-95e3-7079a77155df" name="s3" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="142" y1="288" x2="213" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="646ab72c-9789-4271-8fa9-e8d416c0dde2" name="s4" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="213" y1="288" x2="284" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="635bb570-ae3b-4789-b519-1e2bde2efd2f" name="s5" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="284" y1="288" x2="355" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="8bb9387c-1e83-47b5-916c-d606cd68dec3" name="s6" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="355" y1="288" x2="426" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="481c9b0a-76b8-43d0-add7-e76075b61a9b" name="s7" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="426" y1="288" x2="497" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="6d452f50-9a23-43ed-afa2-0dfaf51b9847" name="s8" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="497" y1="288" x2="568" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="7e1e4dc6-176a-4a3f-82c8-1079d5662375" name="s9" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="568" y1="288" x2="639" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="9aad43f8-e1fd-49a8-9f80-e533bc9e57d9" name="s10" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="639" y1="288" x2="710" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="c90327e4-cd6e-4dde-83c2-1723739225f4" name="s11" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="710" y1="288" x2="781" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="3b7a097a-c1f4-4190-ae55-d189a19978c6" name="s12" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="781" y1="288" x2="852" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="00d3d8c0-a9d7-464e-a4fd-14c23f94184b" name="s13" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="852" y1="288" x2="923" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="c89dfa47-5c49-4c56-836b-19b7dd6e5651" name="deck_1" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="923" y1="0" x2="994" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="9e231907-746e-466c-b50d-1eb3db576f7f" name="deck_2" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="994" y1="0" x2="1065" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="492b2a75-4ed9-431b-9c7f-c93330a33eb6" name="deck_3" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1065" y1="0" x2="1136" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="e98cff41-88ec-47b0-bbd5-e684c97dc483" name="deck_4" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1136" y1="0" x2="1207" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="c6c3c2d2-620d-4fdd-9fce-bc6190817c12" name="deck_5" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1207" y1="0" x2="1278" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="b38f68aa-20ee-4be7-9840-136b838f7d8f" name="deck_6" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1278" y1="0" x2="1349" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="8c8ed8dd-8d4e-4c70-a267-ba27d105b572" name="deck_7" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1349" y1="0" x2="1420" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a4ea2b81-8403-4b5a-8413-e54da2e9b147" name="deck_8" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1420" y1="0" x2="1491" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0e995a0b-0ab6-42e7-9ed9-d4900cfbe304" name="deck_9" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1491" y1="0" x2="1562" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000197-ea3d-4912-9e83-cddddaf0d7dd" name="deck_10" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1562" y1="0" x2="1633" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000198-ea3d-4912-9e83-cddddaf0d7dd" name="deck_11" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1633" y1="0" x2="1704" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000199-ea3d-4912-9e83-cddddaf0d7dd" name="deck_12" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1704" y1="0" x2="1775" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000200-ea3d-4912-9e83-cddddaf0d7dd" name="foundation" layers="69229e15-6b70-464b-806b-889732943899" friendly="" x1="1775" y1="0" x2="1846" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000200-ea3d-4912-9e83-cddddaf0d7de" name="talon_restart" layers="69229e15-6b70-464b-806b-889732943899" friendly="" x1="1846" y1="0" x2="1917" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000200-ea3d-4912-9e83-cddddaf0d7df" name="talon_end" layers="69229e15-6b70-464b-806b-889732943899" friendly="" x1="1917" y1="0" x2="1988" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="8eb12cd0-f6ca-4f8b-9d38-50ec2945dc41" name="backside_empty" layers="69229e15-6b70-464b-806b-889732943899" friendly="" x1="1988" y1="0" x2="2059" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="5a0c5e61-f5b6-4db7-a2a9-405e5fe3c039" name="poker_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2059" y1="0" x2="2091" y2="22"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="1a6307f6-0806-496f-9b4f-afbd7cbd7bba" name="poker_2" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2091" y1="0" x2="2123" y2="22"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="b1ea9525-0e4e-4fc8-8a63-55b2bcf77a95" name="castle_0" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2123" y1="0" x2="2149" y2="12"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="1e9ff1c3-ee06-4142-90f5-ec081d91060b" name="robot_0" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2149" y1="0" x2="2173" y2="7"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="2fc57747-aa27-41d5-b348-ed2be68fa384" name="robot_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2173" y1="0" x2="2197" y2="7"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a0531693-d6c6-4c30-98da-00dfe117c8b2" name="sun_0" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2197" y1="0" x2="2211" y2="12"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="9200227b-5948-4d98-b64a-17846b71b717" name="sun_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2211" y1="0" x2="2225" y2="12"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="fdf06b20-0845-4acd-9661-9d01d8efee4a" name="deck_7_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2225" y1="0" x2="2296" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="bc2a584b-5149-40cb-83d0-a5e67a69f9a6" name="deck_7_2" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2296" y1="0" x2="2367" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="90e8f2f4-80e9-46f3-b56c-f60c072aed88" name="deck_10_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2367" y1="0" x2="2438" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="4e0b645a-38fa-425e-bedb-c61c1061f714" name="deck_11_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2438" y1="0" x2="2509" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="722fa391-4a49-4ddf-b954-4a189cd2f562" name="deck_11_2" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2509" y1="0" x2="2580" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="e2ca4158-d3ec-468b-9afd-316d2e160587" name="deck_12_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2580" y1="0" x2="2651" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="b4d443c9-6606-44df-a294-a3a78e57bad2" name="deck_12_2" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2651" y1="0" x2="2722" y2="96"/>
================================================
FILE: assets/editor/solitaire.xml
================================================
editor.models.TileMapModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tilemap name="cards" rows="4" columns="13" width="71" height="96" x="0" y="0" UUID="dfbe0320-8dd0-485a-90dd-4e6b62b838c9">
<tile width="71" height="96" name="c1" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABQ0lEQVR42u3dQQ6CMBAFUO9/aVypaGgZiobG/5p0YVj5Uob5BcLt9hqL+ZxvYzFeYw1EowNEYhQnFQ/OGZx1BYcDp4azRkkEgjOCs4WRBrSLszWjcSrZIx5nrw7F4fRWSNrqgSN4whEfpuuQU4HgwPlRE/gJowksTjhw4Ayl8sfvxJ5HEwhHtoIDBw4cOHDgGHDgwIEDZ8KgOxXObBtq0+DMuOMIB46a8384rZuGM2zqX4oz8h5UBM6ZF8XgwIFz+Oq0VYyjak4vS7Xu3Quegqcm0H4OHDgGHDhw4MCBAwfOH/3xpfCYMZx4nKOvLcTgjGzDwoEDpwzUqj+xNWdvFbmUw4GjCZSt4MxXZyrH4MBZmo1e71hsKq88+xNXkI88FBWLUzndojvkYh2CA0d8gPM1IPEBjuB5CQ6gDoxPODU+4XQHPcK2ncUHeigAAAAASUVORK5CYII=" UUID="e984a82d-092a-454f-984e-e38606a7f7e5">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c2" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABbklEQVR42u3dy5KCMBAFUP7/p5mdzlTxCOgkN/RJFQvZgEfs7kQbluU9Vttr+zNW4z1+A9E4ACJxBWfrewhnB6Yi0CHO2T4xB87SBAMHTBtOb5i0D6IplfeGSQE6TeU9AnJq6RCRyqfE6XWycJ4Sczof/AVx9roMzp0FqBJF4CcrdJFF4DdPDg6c72enrWAcFXNaMsh/zaW2qvKYbDXyEo+feI7CmaIITK47ImflKRWrlUA4D8GpPODAgQMHDhw4cODAMeDAgQMHDhw4cODAgQMHDhw4cODAgQPHgAMHDhw4cODAgfPwN742/M0YTnmcq20LZXDuNLzE4IxoWZoGpzUOlMHp3cZ4tT0zqul+L0j2ar6NyFajW6fhwHloEfiIG330rljdImZAEdh61cbhpN4GqzRO477aOPGz8lHZ6eh4ZSaed45nJdBK4L0rtDTONEXgaJzSa8jTFoGppYNfPMWcz3AAHcB4hNPOI5x+AJD3TPnKgWPYAAAAAElFTkSuQmCC" UUID="0adc2583-1a94-48fd-9b6e-b1a141bb76e2">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c3" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABhklEQVR42u3dwbKCMAyFYd7/petKwRnahgrtiefvjAtdXPG7kKQhjtu2r8Lj8/hahbWvIxAaDSAkojitaxEccPo4tefEHHOYLo4zTBPHPd5cxnkaSO0fEa5znj5oxTNVAke1dAilcnAWHmwqnBUxIHXMmZGdes9tcEYaULLbhzsP7pcOndz24e6DAwec+7PTWTCWijmRDPLUXuqsjySTrVae4vIbz1U4KYpA5bpDbm9VC5LWzS7VOABOFhznBQ444IADDjjggAMOOCxwMuKkHUGZCcPt4A6M9CABOOAQc1LiMIIycCmlGUFRgZEbJAAHnBwx5/26SvaSylZHHOtsxcaTIpB+DjgscMABBxxwwAEHHHAcPngJjBmDY49z9WsLNjgjrVibfk5qnGgcsMGZ3UO++vVMy7sP0b9ted8KHHD+tAhkymKgYmU+Z0EROPJ+ljOB0fezxgm+Bg4xJyvOzOwUPUvpBIIzdolZ49BDDsJY95DTFoHg5EUEp4UDUAOGn3Cq/ITTC5k90Vj3d4l/AAAAAElFTkSuQmCC" UUID="2373e07c-6a5d-404a-bf07-14b2211653f1">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c4" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABh0lEQVR42u3d24oCMQwA0Pn/n9anXV3Y1k4vsZdT8EFQogeTaQoZr+u1Hh6/jz/rYb3WOxCNDBCJWpyfPISTgIGTgYGTgYGTSCU4mRoDJ5NOtekVkZIjYwzDiahZo2MM2ef0+NVNFANO8yawJaXgnFhzaq8cn57PGqM7Ts2B0owxujeeLSduI2E6/UqvppyGA6f+apB67X+FsmfN6R2juCu/26GnmtaefVBEjKbeqjT4kY1nyYfZchMYtYfY6iQwKk2WOEOOrBtwdsQ5ecGBAwcOHDhw4MCBY8GBAwcOHDhw4MCBAwcOHDhw4MCBAweOBQcOHDhw4MCBA2fzL14yCQTneJy7YwvH4NQMvAzDmW2kaCqc0ryOivF1nNnHGO+OZw4duk8VvW8PwN583RVW7GYYnYYDpz5G6CbwyBt9tO5A3SJmgU1g7n2hOLPflgpO4fvD0wrOojVH47kCzgrnO58a2uNxjj8JrKlXcEY3nqvjhF/KVyvIcGZpPHfCAZSB8RdOib9wegLjTk/MArUyJwAAAABJRU5ErkJggg==" UUID="92298418-a59d-44df-89c9-7e0c028131f2">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c5" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABqElEQVR42u3d4Q7CIAxG0b3/S+Mv3TTCgJXyUS6JiS6a6sko0Ix5HGdLPD6Pr5ZoZ7sCoVEAQqIWp9QXwQHnHoduBY5NtwKHnNM+lPfgeKCOjDEMx+OsGx2jOiG3BPfolo4xwHFLyKFxLPpz2JxjNXLcvVaNYY7TU1BSjNGEUxPoScVtJIzRWXo86tPggNM/GuTe+y9RWuYc6xhNk8DcaFCbn36fW49WI2JkcSxP13ALTyuckJNArzlEiGJXLuFR7NoQBJwZC09wwKGBAw444IADDjjggAOO1BeSWOhK4agV1GRwFCuO4IBDzomH43lJyVI43peULIMz48IAcMDZNOe8jyuMXnKj1RVn69GKhSeTQOo54NDAAQcccMABBxxwwIn+w2t2AoGzPU7rtoVtcHrKsMNw1LYUSeHU9muvGNNx1Lcxtm7PHLrpPpf0Zm+AbXzf4ZbsFLZOgwNOfwzXSeCWN/p4OgPlFjELTAJ7PjsER/22VLWf3Rqn8hg4rqty5ZzTiBh/4SmHs2JthzJpSuD0YIEDDjkHnMmI4JRwACrA8BdOmb9wegFk7M4xQqf0nQAAAABJRU5ErkJggg==" UUID="76153ba2-26c0-4c1e-8ba4-2b558f948138">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c6" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABp0lEQVR42u3d2w7CMAiA4b3/S9crT8u6tQwYhb+JiRoNy5eWHoZx276t8fg8/lqjfdsvEBonQEjM4uzHITgdGHAOYBhWHRxyzkXPYVgN4lQDGh5WEhwPVMsYZjgevc46hgmOx7B0jAGOW0JOjaMxntPmHK2Z4+p11BjqOJIDpYgxpnBGAt05cbOEUeql260xDQ448tmg99mjRKmZc7RjTC0Ce7PBaH7aP9eerSxidHE0u2u6jacWTspFoNcaIs0ZssXqc1kc77wBTkacyg0ccMABBxxwwAEHHHBo4ETASVmCon3R3A6+uOhUhQTggEPOCYFDCYqgm6ctQbG66OULCcAB55mc835fa2YJUYKiuc9JVYLCxpNFIOc54IADDjjggAMOOOCAAw44ThtWcMA5Brm6Y1EGR3KsWuY8JxTO6Lj2ivE4TvQz5NmfZ5a8+zD5uVr3rcABRx7DdRFIlYVgBUp9zgKLQMl3S9YEjn63NM7ge+C47soj55zHcaJtPMPhrHi2wzFpa+BIsMAZzFsMK+8V8oo4rnurRIDgnOEAdALDXzh1/sLpBW0yUp87IyDpAAAAAElFTkSuQmCC" UUID="dfe50140-8e9c-41cb-b21d-c0af51b78436">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c7" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABt0lEQVR42u3d607DMAyG4d3/TZd/DBBtDvPhc/xGmsSmCm8P1IndVHu93uPi8f34NS7Ge/wEQuMBCIlZnKdzERxwxjij18g5TWHA+RSnK4w7TkQi94zhhhMx03nHeMSxhLF+84ExwAFHAadFzvl05hg9V41hjrPTUFKMsTWVzx7j9eYjYmzlnFEgcMDZnw3ujv0vUVqvui1jLPVz7maD2fz092fr2cojxi2O5b/rcYWnFc6Ri8CoNUTZfs5MwqPZlQCi9AeQwlE7dWWueCrmNnDAKY5DzkluXpXCiW5elcHJaEGAA07TnOPZvCo/W3k2r0qvc8oVnpEfVK3b6IbD5eDEOil9IwE44JBz0nHYgpJYYcttQVGpk+Q2EoADTk7Osa6wJbagKFfYqVtQqlXYFJ4nFZ6nDHDAAQcccMABBxxwwGGAcy3dZAdOe5zV2xba4Oy0Vdv0c6RwZs/rqBjpOOo95NXbM1tefVg8rtd1K3DA2Y8Rughkl8XGCpT9OQUWgaPfEYZTaU8gOOAUwamSc1L7OepbUGh2gWMPAw44czCte8g7ywBwwBErPE/CAegBhq9wuvkKpy+t+kiblbFRJwAAAABJRU5ErkJggg==" UUID="fb2435a2-6492-439b-8d34-03e8229a3b8e">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c8" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABoUlEQVR42u2d4QrCMAwGff+Xnv+cytK1XZKmyRUGKkrgWL8mJ+Lrda6D63P9rIN1rm9A0GgAgsQInKt9CBwBTEVATTh3rwEHOMAhkKMEshdUyzom28rrrrOuow7Hqw3wqAMcz8xJD+fpfk6dORqnxt3z6HXU4MzIpMh1uuHcFXpi26zBPAXUhNNTCDjAmT8NpPddhaR25ljU6W4CpdOgJ5v+H1ucVlZ1LuFo3a4pB08NOGmbQK8eIoXPkQKvtAn0zg7gZINTeQFnBZy0gj16/7Gd7CqlSYEDHDJnORwEu6Kli1xHDU5ZTQoc4PhnjuSBthfsWjNOOsHO4EkTiM8BDnCAUwMOgcxRThPI+MDgCRzgbK8s0KSNUwTBThOod+tbg+EbT+AkhUPmMHiygAOcue0IHODIo0cLVBk4M8NqmT4nDJyIfc5SODsI9tGfZ5YU7IPvq6VJgQOc+TpuTSCCfaL7RLBvKrsGsqmWJu39bGk4na8Bx20qj545S+FEHDxDZM7OfgdNGmFbAQc4tXwygUwgqwEETgsOgBpg+Asn4S+c3h1gPYq4VB3UAAAAAElFTkSuQmCC" UUID="0a0ecf52-7060-42f7-9d9d-72ffe26c8907">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c9" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAB70lEQVR42u2d247CMAwF+f+fZt+WXdSmduI4vkykSlAqGYb22D5N4PX6jDfb7/ZvvBmf8RcQNAaAIKGBc3UdAucGTEdAQzhP+4ADHOAAJ4oge4n4zjiqVC59A15ZbncccREoDe5VBnjEEcHRBG4Bx/KSKg8nkhaE0xxt1nh6Hj2OGZwZMylyHDGcp0ArbttuMKuAhnAkgYADnPlscHfclUhaa86OOOLG8y4bSLTp+/GObLUrziUcq9O1ZONpAadsEehVQ5Twc+4Er63ZdUI7gFMNTucBnBNwShvskeuPdGZXK5sUOMBBc47DwWA3dOkixzGDY2lfRrBjQ8KZeb0FnNVjwsIZfetS43v17HI12K16HKnxvapLbgb7qYbQYlZHqcZztWZJ13hGrVnSwDmReYADnORwZmdnaGuWtIKsvW08W7OkSOVeHzRdEeh1iaRrH9rO7AIOcLBJMdgzWBbcmpk49b1aEOAApxgcNOewt5vaQ64ygAOcJXsEOO3haJcttIGzMMGhfp0TBk7EOuconAzLGLXLM8MY7J5xlMf1skmBA5z5OG5FIAb7RPWJwZ7Y7BJqUz+bVCHcveAoq+qecNAcBVg3Pyeywa5cTtDyB1ml+4ADHOAAx6UM4NbMiVReCCBwRnAANADDXzjd/IXTDzvZ38tP/aThAAAAAElFTkSuQmCC" UUID="7a84e376-6c0d-4ba6-a27f-adcf2a41476f">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c10" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAB+ElEQVR42u2d244CIRBE/f+f1qddLwk9DPSlgEPig8aknTMzTVUJ+ni8x5PH/+NrPBnv8QkIGgYgSPTA+bvnfp//vn48nFazAo5xFQEHOMABjhqcrAYeWScETtYMF13HHY7lV6LBeNcBTqYIPAbOqH3Ytud4zRpXz9XruMEZCZOU63TDuSo0k7ZFg5kFZMLpKQQc4IzPBq33WZLA6xYeqTMFp2c26DmwliTwOhEjdVx0zuzleudDztwSJcZzBk6Wqi4zntEaosJyuPYcq+GtZgVKROAqVmApONlWoNR4qgs64KwCJ8typOqcrJnHwwosE3aNaJYZK1ASk1YKuizLsRwcxSC/rOcQsBOwHxywqwg6uZgUOMDJ7zkRwbdEwO7lpSKC79KAPcN4rlIHEaiW56wygAMc4NCQt2jITOWIQOwDxhM4wNGPLIhJjVmEgB0R6Hfpex4oXwefDOe4hQS9t8iRS1Bmz/r2i5cUBR1wdkkCq8Hc2KZQugi6ZJG2PJxsVX1324IEnOrNJ62aEt4qw3LIwMm0Ah4nIBzOCtsY727PlArYKzbadrzvrJgUOMAZr5MmAgnYCdjPCNgvdI1mhlwV5F/8Rtl5cCxhCJwKOOo9pxxOdFA1U0cCjuoADnCAAxyFvBo41fZhQ4jAseAAyADDXzg1/sLpBbaw5qj3TuKAAAAAAElFTkSuQmCC" UUID="f690e1b0-3c1b-4fd4-8754-09d5823bc76a">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c11" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAEp0lEQVR42u1di27jMAzz//+0Djega5xIJGXnoa7uEGzrFmdhaErWw2vt/bJ1/B7dy9br/doCtNAAAC0ksuDs5+ECZ4GjT6tvBWaBs8BZ4CxwPgKcT14WTIGzHyQCRxz4/xc/x/br7feNvHd4f5IJt4AzC1AbeY9ch/1Np4GD6Kg+ye78zTltd9PovQwwTfjdac1hFzs82Wjg18dummSOxq6R0JZTwJHmLgHoF5wLRbQsOHTqWQ+OJpYt+FwMHDqt7H240/IGcEpoTmytADitv8H9WN73KjjKWKMuyCngIOZ4bFBuRgFnRrNOMeXxk9WY408V/AQVcGZFfQqc3EUwOEc2fBE4THPY1Pha5qg3qILDAIrk4BLNiS6IrBGzViOmPGInu/FLrZW9/qQff8VSfs7xhhoUemzZfPY8tvBUncCIGXsGZG4uOk/xuW5zAtUpwJjTAUMWsd50QrrySDxnFJxwzoOQhb+A9cftrjEQ2TslEpi7UBAYc2IzYSgCABdOXzGkwZzRC5lj2PnzmPNiBQhwxVbTAcden01aJD8essgwB5llz6fqwLEe7FicHwCHxpwF5rDx4pvbgtK7CyXAgaY8ozmBaEYm/WOYkxFlqDmBNeoAQJrTCjIn9HNmNKdhR3BvrUqC4zEnAgYxx1/D+cfHMkfJR6EspucpQ+bU1hy+BmrQUgXaZAJzqlsr5ubvGeFbqiYzhjEnyq6WAAf5QRFzjqENkTmO5nQPIRj3UXBCv4WsofY3o4Zpo/HLMScKKyjMUQ83+ueMX3JahaYZaI6F8ZtezKNKDyPMLM0cSW9ImQus9LCj9mRSSI8yB7LGOV9JpTSoMVYPHDiljE2huKwFiryzykfediknkC02EctgAhGMW9IJZNPJGksbm5Q8bPJ0Kqg5kXmVQqogK4GYAy1lpWllImtGTDkLmtVlDnDIWH1PxglUhLgccxSHTCtU4OEQJdxaTHPIQrDt9UFdaGrMicx+KeaEppwEpdwSFC9NLIZbS/s5LCilaIsb60n4OLXBEZkTM8bCZhGlNSBT6fEMOEnNyS5FwlRQlAAoBw5JoRwFNxluDZzJP8McDxivnOVgqUgxQm3mEM0JlyCJcCtjzmmdek9YK1SYSQP1YhnLRzFHAWZGcy7p8cyCw8IQSHNQvbCaUkbMUYqtLgNHbffxrBVNwZASOcQcpDs35sqTvw+sEVrINpTRZOx7srJrvDoj7stCWQk/M6GX/T4KzkhRNcp65mqE8qZc+Nm5zMk0r/qlbayfwjp3ARZSyU1zFzFnpFHDd+3Hm0n0fgo/sA/ciflq0tEWn5H2Rd9q6u2Wyb6y8bK32V7xbONr7FJgcFr7YnAyLd5fyBzuyQ82+p6rOZntF5T2Rdx6rYGjtmFfbq3UJniFObSqy7T+95FGttudwCw4kj+13W0lmZq5zQkc3cnkLHA0T/yh5cPZ27zIh/F9fuzTwGHFB9LGQxaDsweITatSmtMmtqqyrK6lFsEFFp6zm5xlGmGVjdluAUc6/6Tt8bIA3QLOX9hEUTQWX7295gJngbPAWeAscCoAI3jwC5wFzhiACxwEzgIIALP+hVPwL5z+AU/k+yNm5kkRAAAAAElFTkSuQmCC" UUID="ab0658dd-6958-4503-85ae-380dd54ec310">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c12" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAFIElEQVR42u2dCXLbMAxFcf9Lo+1M21gi/kItpJTQM26mtmNZT8DHQtCJ+Lrluv+/b265bl+3T0CLBgG0SLhwKh9ccACYnwoIwqksacFZcBacBWfBeRicN5cFQ+D8/te7f36wv+8Rxc/9Y3dZwjDLOQMHAToCx/3dy+Awk9yeWHvfwCOWU4E6eAIWoNNwnAMqF/sE47gVeuwIGAboEjjKZC39qcAIC+l1relwsCAnBwSsRn3IV8BhB0JwkNZUcCqt+XztGUCP0Zw9HMelVO6UxkkiuOzCzhfk0FajQDNAZxK/S0I5uhpH4CQI4UfgnM2MT8HpPUijNQRML5wK0GvgfFpMBSZMMKXpF7BfaTkUzAF/b6AYgJAc3KI5YUQWpgUlGKEhyKUUIKSDt0Wr/Ocwf5408wXkTo7IMsgI0JTC82gSyKxmL9z4PaMb0PAksKcvEqp++kwMMzCE3L0+a93pzaCnwQkjn2kAFZD2z23AFCKtNGw6HGT6MMpFmyhu7qBBFsitQAqQHXJwO5z9icncKHjXMEWyWcFxxXsonKa5tfm/1icovEDMG0A7i0kjqg3SnLr7t/0pajUjyUNW6ZQcl2bIXXDg1cIu1rhKAPEt2iBlZBPBou5rT4KTwN0oHHC1G6ss4HALwceeBieBS0CxDpLzgKK2siQVEIZrDjuRBCeLNUQ8By2nDQQoSg2PVg4oBsGyrtTWo5asp1qOTADN10LrIG2RKhU4uyZ2meUoi3Ag7d+rAtP0eb4+0P3NrqOWI11CdAyRm1Zw2iI1aVkx1XJwDlJDCuFiSIwTHBNFyrnRSoXfxK7nhnAEp4a9dbGptZUbPVBSmERnqnYGstSm+1heuBlwdmKYhplX1iPBKqshLpbTMuTClHcvlCUFg6f0KEAjn0WxcYIs66Pk2uJceVD17/WmLWh1FLtdkLNwsY0JGydZwarXrbDlhOjxDLecIAkZN+1zblUC3bmz/FwjGuxKDNOMWBSYAEc7A4VVT1+aSSesHwznysLYgMNQOKUFNaZMWhtuho0yZCP5jFlwaBUOml89RSjKd46ULeM1pyuKFfDMk4yOXnXdBIt5K540pd9HEAMOciHVikXD4lMtp1q807M7fohXncEw2yDTLIdPPvidwZ5WKqrYcZU/U3PomlG7jNzVZE+y7GODmh2tkkeUHnFV+ZJqfuWsPKfu1yQdsER5S7kAF3y4QJYUs+C4a0ZsckutravlYqdXPd1y0CgIH5A01tSNYUm2tl7r1ODywRkFCSKW5diJ0B6kf+W+rxnRivaUEZgwXCnMbZFGQMgZnUDVHXSm0ulonBqPM0ft4glwWrHGSznI5RooarASuRhww+lw3LWudmm37tuw/k7lZs9YmjFbqHLik9ZDSaNdFC1ZFS35ew6wHDmpbgHSjbUAveOeUVu1jeGSaNWzUcMBxCCXnT1DgI9sgLmhttJbfNTWoa4LJGZ3nM9EZqfvaXZZ8zJiKt1yEaNjqHKjx8FxS44eq8HziPk+OEpkKRy4p9Sbpme12m2a0/sdNj1wHHdy4Ki047Zo5UYdPRCFIwuzGpQrTYHTc8AeOBQuqMtYIjk1CTz7dVBdm8mEOyk47p7Qx8GhVb8pxF3bm94Ax1kTY1ajun+v1RwkvqglwbQGATry9VtDC89e11KjdGl+sRqLgOixYf2c48s+hlulD2gInLd/ieJtcL7DbcFZcBacBWfBeRicy5PA7wzodPnww+AtOAzOAkTArD/hBP6E0y93kn6S/56mSgAAAABJRU5ErkJggg==" UUID="58d2569a-ebd9-4049-b662-7c2dd68968c9">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="c13" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAEUElEQVR42u2di26sMAxE+f+fdqV71e4S7PE4CYkRRlq1+ygLh8nEcRx6HJ9N6vH3OG1S22f7BlQ0AKAi4cFp2531WsEpONdm1UJ4IxQKzpvBQDhvB+PCqd6qlDPXc548LLi9t5qhqP/7+Dyiz7WHd1zT4VjAiJ2SB9teWf+5DU/ug8MGgehLolL+937wocHj1boJTm9bD/tGA4qFMtysRq7AKjgj+34UnKPj6kf2/d10p8FBXz7LcyJybz+7DY53gujzzHNPDegYrJ8p4Yx4BAJgBaNb4aADZNp/BJB1gqgpb4PTc1IFp+DE4bDGiTwH7S8VHGR+aGyFTrz3fdaImZBjfpzz+2bHGCUS/KEeLKoc+DdvCgKRim6HM3N064H3jNnzstNnLP97AhzvyjP+Yn7GGLE/Dk4UElLXHxh5IBxmKGKdyAWilduRB8HpSWCpiSwjySXGa9KkVtPA0a5om/e9NhX75L00qdaUxPKlnXAO52RaQDCJzuSOtaYkybry88GK+RMp6Hs2wVLfJaEOeifVxFfDYa42qyALEAVGcJS9Ho7WAxE/LUDtXJQG5gQH9Ez54ETNVOwmA39XvMab688Bh1DPAQB5075ek9KGEGmVY8UyR+NBXs/FfI+bW1oJR+19Lt7CBYZaE4LKEWzE+5UDJE9VbRjqQcpBfuNmLncr5zDGNNxJYPWwfuMOT1bBoXLNBhwh1SMdfgOD1WXKMYyXaVYwyPOUI6CnylBI0FP9MFxNQXiOBWq9coLTw6im5nDCALVpCQdku3LwlEr8xD2A7Fz8duUgWVvZutkFS0uqSUfTnlA5oo/QNSOOVGlARStBYQrPQUkwPZrF6YmjM4ZaX/YWmlfHuR1NMTivY8MRkOpYDocN3S3PYdIWVtNQFWzAj5Tk3gJHzB7j7DlwYBlQj9qTfQ180yqH8RtPOa16rL+JfE8a5bB+A9WjAuNTsmngIA9Q/YZIi2qAupYBZIKjhfrMDIQ5TXNJeAVTshngqEYM0hORKRqY23EXkMhmz3FmP8MTd6JPH5+6ZcJzBgsgxuGYijHGU5HlQj0Kilak3Q7HSmXK4PyWVYBgBYmo0GFjswJX0RlbodKTWSPufcoBhUSo1/CKlrwE1oKiqxvgkCtlzOouEbegO7rlgOOceE+hZO8JpvEcawjhVcUzdYIzbgKQoiuf1Txmr0buKUafCseDwRht1Hxj69hjr0+H4zUnq3l5oEbeZxWufGY+nFnKYebFLH9C+2NTvCngRI15dPFbwSk4cTgoTEjjOcw69EiVRmR6ODCtvQ9Oz41FRhf5p4LDLDx7TRDIHHxk5U1k3ynyORlv/5In2RVpGmRdzSo4Ea+7baVeZLlQJBc86jnb4aC7tUVvYDb3BmrijummNCsWDJr2jT0f20gYcw35yTdRPJz5t9ffSduJxusmrQWnlNMHpjzHGZ4UnLuDwIJTW8Fh4BQgAKb+hZPxL5x+ANT8pnj4fc0QAAAAAElFTkSuQmCC" UUID="9a666fae-7f67-4afa-8c4d-142d62c782d2">
<layer>41e4ef0d-8383-4cc2-a31f-01041591ab40</layer>
</tile>
<tile width="71" height="96" name="d1" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABKUlEQVR42u3dQQ6CQAwFUO5/aYwLBQ3UMsRQ0tdkNrIxL+PMb2PCNC01W+/1UbNaag1EIwAiMYTTGA7OMM7z89eCAyeHs0ZpCgTnMM4WRkOgGGdrtcaJYJoB7eNEt1dLnGiHNNw9cDSecDSeeqvbtA+NgSRkOHCcOW4rOUdChgMHjoIDBw4cOHDgwIEDpyBOke9TD6fQDKkWTrHpYx2cgnPrGjhF/xMEpzROBuYioGtxjsBcAGTnOHPgyDkSsoSst9KVm+fAUXDgwIEDBw4cOHDgwIEDZ0gAzi5MMFjri5MYyfbESQ7z4cCB48xxW8k5EjKcWx7AiWdw4HwhJJ/1xBECz6Xj3jiJn1vv3urHOQQHzvG+Cg6csaYTDhyN519xAAUwXuG08wqnB2JR4Y6jlmo1AAAAAElFTkSuQmCC" UUID="d59b8ca9-5fa0-4f46-b7ac-93869a30117a">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d2" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABXUlEQVR42u3dQQ7CIBAF0N7/0jVurBppMTHMR94kbLqpeVAYaoZu2xG79mgvsYsjnoFonACR+Arnfu29wWnALAh0jnN1zZwDpw8GDpg+nAqYoE7oW8pHwgSN0uulfNSEHJg2ZCzloXlVfRJ4dq9iIDixG88emEKgOpxvYIqArvMcI6cwz/mLCRlOUc4xTZ5T9UOnyJAre3G6vZVd+Rbfi5nvcxYO/1vBgQMHDhw4cODAEXDgwIEDBw4cOHDgwIEDBw4cOHDgwIEj4MCBAwcOHDhw4MCBAwcOHDgjFeA0YRoFMHk4VWdnfADKwqkqfGsA5eAklE1G4oQW3dbjVJZOx8851XXlsatVynENcXnO9Ad9rDJyYrcPFTCd94ITnyGPfpw6D2xbM0NuHfNnb2VX/pPOWPd9TscoXROn8/FdGyc+CUzBiV+twvIqE/JUS3mUH5xLHEAnMD7h1PiE0w1sZiBQH106AQAAAABJRU5ErkJggg==" UUID="9ec05b58-89fb-4689-b800-175cc034fab1">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d3" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABe0lEQVR42u3dwQ6CMBCEYd7/pTFeRA9tlxC7M5m/CQcugh9t2V2qHMfVTrbP9tNO2tW+gdCYACFRxnnvjzZwwFnjjPaZc7Jh1jjBMHOc8PnmPs4OIKGLUI9zduCI9VIdHMFhXLuV/xtHNK7qDwJnx2oG6r9bWeLsOXp9i8K5A9MEtI6Q6TlHX8xhN+fsPEErnI6TtIlzuk7UIkLuvIoWuVXnVbTIyoPrOBpxjjVOeAMHHHDAAQcccMABBxwaOOCAE4ZjuQRlF4zlEpSdMNJLUBRgZJegqMBEr7IA5yEMS1Acl6DQc5hzwCHOIUImtyK3IiunngMOODRwwAEHHHDAAQcccMBJxxl8f3AmNaTses6i+phbCSzUrXNryDY4oj+6zX5uJT/ndD/Uk71bqTwrl4tzWGVh0nNk04cumMJxMnGKx8qMc4p/2JYZIRc/PzO3ssTZlZUXh3BuPQecZ0MsG2cx9zCsLG7lgnEVOOC4Jp4GOABNYHiF0+AVTi8I8FMP4EHiOwAAAABJRU5ErkJggg==" UUID="d972f5c7-687b-4f65-aa17-f297ba99bc0e">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d4" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABgUlEQVR42u3dyw6EIAyFYd//pZ3MYm4LKzrtgQM/iauZBPIp0JJUt+3Tdq739dN22qd9A6ERACFxG+f5+4KA5zgvGHACGHACGHAOphI4wRoDTjCd7k4vFWhRP3U4qrWqsJ+aOEe1mBf3k4+TMR0H6edaENiKWD1wUT/gSBLPlgFnDFzVTxrOlQH/M3BVP825FU/OlhM/LLHmZAR+U+JkRsbTxTlZOdWUEXLWHZk2t8rceqfKytV33vKYVHnnLXEWb+CAAw444IADDjjggEMDBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAoYEDDjjggAMOOOCAAw444IADTroCOIcwBwUwtTijlxSdlE7V4YxejNZQdFeD41DG2AXHpQBWjuNWOi1dcxzryiW7lfPrGkrjnCVf9LHKkyNJH1zWnMb/giPDcYxz5Fm5U4Tc5chi1NzqwpRcLysfBsfhHCd4AsEJpiY43U4CnXCku5UTjnwrd1uQwemZeE6KA1AAwyecDj7h9AAwqoHSxffH8AAAAABJRU5ErkJggg==" UUID="4f89e5ae-3554-4465-a6df-c409b5030fa2">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d5" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABjUlEQVR42u3d0Q6DIAyFYd//pV12M7dkaBV6aOlPYrKLmZJvWqGK27aj7Wyf7afttKN9A6FxAoSECef9ubWBA841Dg2cMacVOOSc+5fypzgqUKc4fjiqI84xji0h3w2uOiWd44zHUeUsQZyxCfls35EdF8X5n3OeBiuB49XhER1XxRmGc6fDPR1XxTHh9F6hlj1yngQhIVfG6Q3IOKf6CLk38JJzq5G/yLKz8uKFrrHjnLLFLnBo4IADDjjggAMOOODQwAFnGZxAfYmFE6yGFAcn4MNSMXCCPk02H0d1rwuclXCE971z4YifmODIIeeAAw7jHEbIzK2YldPAAQcccMABBxxwwAEHHHDAAQccqQI4TZhGgc0XJ/qSoovSrB9O9MVohqK+D06GZYxTcLIsgJXjZFs6Lc05GdeVS65WmV/X4DrOKfmijypHjmT6kCnnGPapiWPcr+Y4x/jStpoj5Kk40edWxu/XnJWHwMlQw5lSssgKBI7tVAMHHHIOOFpDcC5xADqB4S+cGn/h9AKh/rCVN4D94gAAAABJRU5ErkJggg==" UUID="0b1e1a44-f5c7-4fc7-b1b3-1ea0fbfae73a">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d6" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABlElEQVR42u3dS67CMAxA0e5/00VMaJHID39S2zdSJ+ghR+c1wXYjOI5rnFyf62ucjGvcgdDoACGxjPN+/X6B04AB5wcMy6qBw54zuHNYVpM4xYDml9UqjjekQTwbHO87zSiePo73UjSMp4vjvVcZx9PbkHvvsQByiKeXIZfC0Z6oJpBTPDnOykQ1cFbjCWKOq3LunEOeO5TZc/4JVAJHEix9niMNmDpD1vhvlKitJEHTVuW0ShXznJI4xQc44IADDjjggAMOOOAwwAEHnKA4qZtd0ommbZNqTTTlERTNiaY7gqI90dKnLMARTpQjKJWOoHDnsOeAQ57z1DyHDJnaiqqcfg444IADDjjggAMOOEiAAw444Gyu6MHp9IJq93MGXcS6ncCJ/nPdHrI7TqSnD644EZ9bue05UR/qmX9aRX9WbpbncMoi+Z1jXj5E23Mm/r4mzuR7auY5k1/YVjND3oYTobbaivP0qnw7TpQejnvLIjIQOHNLDZzOhs6y2tJDjoZjWlul9ANniANQB4afcGr8hNMLiGLpTmJkAEMAAAAASUVORK5CYII=" UUID="a8f33327-dfbd-4989-805d-88847490ff20">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d7" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABo0lEQVR42u3dzRLCIAxG0b7/S9dxY3VRoEJ+PnKZ6UI3qUekSWDG47jGyfW5fsbJuMY3EBoNICSGcd6v7y5wwOnj9N5jzakJA840TlEYWxxvUIN4NjjeTzejeG2cWRgPIMN4a3G88yPjeOtwWjdqAeQQDxzzwnPkRlcCOcWbx3lyoytwnsabiNn/WTFzjvncocya80+gEjgzwbbPc2YDbp0hr/g2tq6tVnwbW1flRfvG6/Oc5DMgtnxIvnbo4yTcBsqBk3SfLB7HO7MGZwcc72peBsexL8PMYc0BBxzyHDJkaiuqcukPGXIEReHnEXIERWFhDTuCkv2RHHoEJXMyJ3PKApxEBSRHUOJbHcwc1hxwEuOQ55AhU1tRlSv2cxINcMABBxxwwAEHHHDAYYADDjimFT04jV5Q7X5Op4tYtxM40H+u20N2x1HafXDFUdy3cltzVDf1zJ9W6nvlZnkOpyw2nznm5YPyQQLzDBmcjfIcdxylDDkER6W26sBQlYfhqPVzwBmHAQecBzDmzS41HNdOIDhV/MDp4gDUgOEvnG7+wukFMOpn0EycDbgAAAAASUVORK5CYII=" UUID="3ab272fa-f8cd-4c54-8c75-16ce3b5361c9">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d8" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABvUlEQVR42u2dWw6DMAwEe/9Lt+pPX1LDy95444nEDwItGhLH60Rwu73bneN1fLU77d0+AUFjAAgSh+A8z/0ewPkDpiGgMZytc8ABDnAIyFUCshpmgl7OsFL3tiS9eDjq4ZioFwtHnR8l68XFnNE9GYAEenGzVSs40Q8aCUikdx3OkQeNACTUG8M5M3W36DlHBdrEnDNCLeBcEVs+z7kquHSGHGU6l/dWV0SXdeUz3r5VsWvG25+lmWofiscOfzgFy7I14BRdJ5sPR51ZA2cFOGo3bwNHXQei5xBzgAMc8hwyZLyVxF2r75PDWboS6BQ75LssXGadKbssHPIVm40EwCnkkdhlEahpA8eo1MGwIiADpzAckkDsA8aTkgXFrkXgUCYtMsvZwGFpBjj1PZkNHDYS0HOIOcAhzyFDxlstByfJXZ/V5LNUg95ar56j1NuIc30rgTtmyL41ZDkcp9UHKRzHdStZzHFd1EufrdzXytPynLYf+ujSc9Ltg1vM2XF9Tzg77+mZ5+z8YFvPDHkaHAdvNRVOdVc+Jea41nHk9RwHMNOGFXCA0yPeAIeAHMgPOJtwADQAwy+c/vzC6QFNggUl0BIMHgAAAABJRU5ErkJggg==" UUID="61c0327c-0e96-4893-8dc1-4692e046bb6b">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d9" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAB0klEQVR42u2d2wrCQAxE/f+fVnzxBm7TNpnmchb6IoUsxzWZGcXebu9153pdX+vOeq9PQNBYAILELjjP134v4PwBMxDQGs7Wa8ABDnCAk6Uhq0EG1Ns3yq0bUE+3oHp2EWgtrh7/gfVscKyF1foouJ6fQl5tNAKQoJ4dToLN5oHjvVFPQKJ65+Hs2agHIGG9NZwjo3vEyTmjbVr3nCOFRsA5U6y9zjlbsLVC9jKdbb2Vx7sxxpUPXT46ZySc4Qs4wMkGp/W0Sqg7cuicpIr1eoWc2Otc760Su+QckQVwHOGQITtstFWGzMmh59SBkyjQz6VzPL/xaKWQE2bWObyV+sSVceXqXlUqz1FPuTJw1PqoDBy1subk0HOA0xvO0e/WE/0oPE/AnvCHC7kCdjXUUsbzio8jeQ5wCNiV9QjYOTn0HBpyTTgC3VFX5wgUa22FHOx16nurQJfcw5U3WcABDnDc+xVwFpNu9rTa0EhzdY5BXc9VyHI4lbyVFE5FVy7rOVUji/BpVT0JDNM5ZMjNT064fejQkIFj10czdY7xvpkK2XjfTG9lPG0zXbnx4zgzzzH+MS1wgAMc4Cj10dyA/dJR3oIfcDbhAGgBhkc4/XmE0wPOU3ugNV42GgAAAABJRU5ErkJggg==" UUID="44672b8c-c9c1-434d-803a-fca1b30ec92b">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d10" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAB6UlEQVR42u2d2w7CMAxD+f+fBvHCZWKFscRxm1NpEiDA26G0jtdpl8uzXdke21u70p7tFRA0BoAg8ROc++NPz7evt4ezBdMM0G9w9noVcIADHOC4wFFDTNDLgaOe2ZL04uGop/5EvVg4e74oC1CyXpwJHO1oBiCBXlz50AJO1o5GAhLpnYdzZEcjAAn1xnCOeJpWPeeoQJsx5x+hVrNVNKCMMqDM5/wreBbMmc/IHPJMBymrraJ+DfXfQ1KVqytr9UAu9zkt4TiAKQRUB0dddtBzZoGjLjuks5Uq042AI/U56kw3umSxhKM+SHltVelXzFIALzgVA3k6HDJkEzNnkyHTc8zGHOuYFDiGPsdQz8Mhm+rV11bGevVVubEeK9iBAxzgAAc4neEwlWMCKR8oPIksgEPATsBeq0fPmWrMWe6kXtTOLns6+KzvWHohQdVBLh2wt1q8VDWwLhews9TWxDzSc1aCU1GwHhivgDPQ8ciQqy4p+qLnkyE7XPy2+Q6vDFkJVQ5npgtgpXBmjEllY86sGXL6bDV7Epjmc8iQF+85U8SkKr2997WfrUbvtfc52Xqj3mLtkBV6pXASM90QvXI4SZluiJ4FHNcGHOAABzgVcOQmcFZA6eXDkgyB8xUOgAZguIXTzi2cbi2VWrO+c2pRAAAAAElFTkSuQmCC" UUID="29e7d023-9cf2-4776-beef-5f390a6fbc59">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d11" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAEYklEQVR42u2d4ZKDIAyEef+Xzs39uKqYbDYBAXt0plOvrVU/lpCE4JVyPGQ/P8/LQ/bjeJwBbRoA0CYRhvP7/vm54Ww4fLf6p2A2nA1nw9lwXgHnzWFBG5wahgGH/fHPvrVLkNjOXGDmvIfAMQFFtiNgnM/7wQEOYOYgtxO3bJnSODQYxz62wyE842z/leQzBAYA6gOH2yncvR4xpMVS+2Q4v2cQMc73C+b2NwEVcVT3JBwgzxY4x35vhUPZnBicO5Rj/7oLuSqL2K2+BlncFkBw7Pd0OAygy7Eyhr3PUO4bOATH6gKechDcdZRDHsTqFviCO8KZYnMCyqllj2zQd8AhbY6cgJxtRysc7Xeg8Z/i5wRGK8ZZiygH7aseZ4qHjA6ijDaeT5RRzvG5032HxVadPGQfSivc0VF5KMrmu5K1jXyQUqUsahuXa9AFlaN2CyX6vnQr5/No0m055cBIO5i2uNqh+ECwhHLOF+/FTIWEgmItBOmj1tnKUU/e6ArZnI7lRtT+1jLKcVuXuFgWiqWc2pbdwM1STgug2p/RXrVuqjug9266jHKkAZBnuDU4SD2lgiOj4ai2IABIsq/GiFWMeG+4cuptzsbogP5alnk17YwyIFx8qyWUIwMUZIQVnvGeohwzcR60QRk4xUmdzFeOMTKg4b5JQVWs5EOaoJzLARX1PGGDLDgI0hTlMH6F5xOlFASUg44xN7YCtufces02yIBj/d7c2IpUjoC5qpCC1G5lTxa+WjkabA+sO/n3NuWgCxIHkGfY9QZ5iXLcuIkEBPNCoLGWVQ6T7GIAof001ayjnGLPLFBRNgBkOnxvUk5xLuhmYwQD8oz3a5RjesfasEsqCCnmVcopxFDLwCmM7XqTcjx3nYUjEUBB5ZR5sw92XsX0ZsFrREGMn1O6VXa15JAFJ59Y5UQV5HnI/dY+JOetQsmnAJyogtBs6HSbw6Qwo2nRDCDbjZhkc8wUpcRsTg8bpNmZacqxupJ6ssnEegTQOsrJTra1TvIRgOZ7yETgqcGJTgdHAC03Vy6OenoWEkQALZHPsUYpZhRJV1sUZvheqLKrEJVYLcVLuPpi8couPBsqTWVv0cVq05TTtBAtUSypFV7GK1qHKSc6S6pUkwpfZgtLdpdSDv4CbEGrfs/vvgUm1ZZRDgRjLreuL0zcilAPCl39jr6Ll2t2gkOsR88sfo0uKAn5Of4CmA5wwkt7UJluHo6VOlGVwy3XbISTWhRmgZBmOFYAnFx09x1wLE9c7Vb8cs0GOOmFqM/AQd1yrHKaljB71RLMUC4huONtTqNyGEDZ5Y+Un/P4aBW0OREvm0mcN8F53M8ZBCe9EG26hxzwc1BA+sT9t8bnkBMespkXeuKGQ6xSfY/++djKPFnn/mDm++nMgPiDyeioHIJpvANcMxzj72fgRMFkthOAhsH5ipsoEncw+L930t5wNpwNZ8PZcBYC49yMaMPZcNIO64aD4GxAAMz+F07Gv3D6ATnnzGBMQ62TAAAAAElFTkSuQmCC" UUID="a582dd25-9a24-46c2-ba98-1927e3e55ac5">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d12" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAE8klEQVR42u2d227jMAxE+f8/rUVftrHDuVC2xbRVAKPobpPGxyNySMppxPdj7OP/cXiM/fh+vALaNAigTcKG8/X9+dhwAJg/CgjDyZS04Ww4G86Gs+F8FpyfXBYsgfP9VR/xYgcCvs54OYH6ibgnuwwOA/R2oic4/OskIPHz98EhJtCB4yjnqJZxkn8RjmFWr8MxyodIv68pJ4c8Cch08/fAEVfEgfMGKVEIeDM1QIHUvgKOiDkZHKaacOKCCydEGIgn4ZjylEE5WU4o1pzhUEBtcMj6ZXAqWQoGY0c9FEpY4WAyII/SFaABWSyn9LWUeipgXgDdkMp1gEOp3FWNC4eqp0U5N/gclKGw6ibgtMScgnLe3WweN1D6zmqez4ZTjDnpshKGz0nl1wE96XOK2UqCMZSTxSvL87Q45MIvseon8H9nFZZS+vLaqlj6KzDnZYez1hFAyS0vqcqLcJAvyZZLBunsZc5wLlXqnXBQ3YQAZSCQqmaK0CgazsfgMDAIEKvAVaBmcDLlOYbzOeUQHzPbx6X11tDxii3P5XDOV5Mtp0pjvtL8Yh0Bd7k9sqzCAOQsN/Yc6IOg4oLbg5VwzqUDA+B+VRlPBd3qc54JyCPkVXOyGFtyzCtpQPnvWpDKUc8mysGZBe1MnW4xG5P13X3LCiwxpCo3k+WwPRXy2m2hz8kaXMh3uA12BdhRSMQHpPI09iTmTZUOFIqlnKBzsJ5lhWKPWTaoYWAGJwD8MGbxLdkqXVYvDXE37qB2a5YI1NLtW1Yk4B5aF6A+UsuJwckCv7OU1lflWWGYKCd1uGK5MQX6MWz0pfIgGYspJ4PjxrL85wdVUKtyYJvzBuW8dwHm/FOrct4a5IWYk77WoS+cpHRRJqACtk05ClA2XcBDxJcD9XUmHXiLchCg/N+PJ6+OdGYOALX7nIqCUjCiOc+gO4DalFNVENvM5KZypZysxGgygQMaLtriDN3qUCYwgFVw1NMy1KuUBGPSBMqYY8aetUM9qiBs9wdZfqzMUMpxupRtqdzpyaDej1ucQgUKo9gGR7URVGOs2tbI/I5W0OJspXZLsG0mTvvBamucANFOQUdVHuLOGrcnU52ZycZYt89xfBAa3TpgWWBW2+/aUrnTQ8EbAny1ueMdNTJqiDnekM3dhzMzRra7lCt7yNlJzjznrvExGwS2NNj5VRtgejmmNhRUZ+utLYvKVhAGcHabiq2gXhOIR7feJgSvYzcDbXTAcca4chOCsATVrXNvgLp8jrPcnJkX2+qmygaqHOPGlGVwZgeCzj4c2P5AQfnCLQbPwikoZxA4akYPA76xW74HjnX/gb+FH508tAok+M7cxnAfHOMGDQRHbTNBgBAYexws3us9cMxbexicSrpVYNCWW/6en4AzdVMYU8+wUjwDw3ZhuBfzo+HAmDMGvbOGbdamISD/tJcLcKZvRJ2Dk4EZokmfv++nlXPpFmbtSxzD6aqmJ+ZMKMdJ6XyLf101fdmqGHOYekpNLWMo2O9zJuAotyx7SUI1Eo7xOWTLfU6lCFXpW825LDi6PbLOIVvLawz+IUSGaryArOPi0tqqCkjXT8ZHYrX5nGJVzuA4e2sqyrHgZBe2q59jtzLILL6iHFw+PAjnx3+I4lNwfsVjw9lwNpwNZ8P5MDh3m8BfDehq+fB32G04Es4GRMDsP+EE/oTTP8f50FzZgg2fAAAAAElFTkSuQmCC" UUID="ad1b28a4-61a9-4451-9759-9565cd7b0626">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="d13" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAEsUlEQVR42u2djW7bMAyE9f4vzWEosDoyeTxSlGQvCmBsTZvU/kIdf+W29vuQc/w7Ph5yHr+PK6BDAwA6JFw4f/+9HtZzB86Bc19WPYQvhMLB+WIwGM6Xg/HhHG91LKdUc96cFkz3Vtmg8ed1n0f/nPYzzMGcUz0c5Tn3l4DvXy/+8xO9f83CasqH11vKuOWQQSD8JYRetf49tYOE1VSrvoOR7XDUi5SUZjGwNOvQoNXACWlH/6QBp0kaEITVbKt5FhwTjA2oOcuuXayU8T4amHpvRQhrJZxmWoBQgJDWfEhDibcihBUGjVBkbY/SFG/I/mt6oylwbpZgwImAuYUD/rKNwlG1xvj5ZJzjC+uo5XgnOQJnruZkLi6gOZFINgJnjbeKWk4Czs8hS6yn3pWzmlMY51SI8twIOeOtEhGy5WkYl84CukHfFucEc6tIMpgFpOVy+9IHMiuPRMiZZfYoOINu0oX2WstBYT6TM3nv/WrL0S4WexFfZ0aE+TFwGDAtUj0srFE/Ag7z6fXFqQwMr+qnxTVzCuwDmuNdcF/NYyzAAuTBs16z3FtBIBeTVgvp3fd1MKgwj5PO/jXL45zrhd6WkXLx1+8hQBXa8wtINnkrs75rL5EeiijLJNO7mtvUiwiyAeVTgPESur9uvNi1X5ABFK2D2YBVjY7doWVc19RLwEFQvI4myqajXQcP0HI4DBQNhvZ1RGu092ld8w95wUVBIIaiibDesWT1wgLcQYGecom3QkvISjW4wBB5PkuAWUBL4TCfEGM9aLgAWU00Al8GxwvkonCY5eS57ZvV9Oe5XHMcQEhrvHGUqNU8B04CkAZnxGrEgPCIINCCxMcrzakKJkZVwAe2PEJuRnJppxR2j8mDA7XGAbS8EqidZAaOZ1nNSWZZa14fIRv/94I4ZjbQ0ykW0KPgMMIa1SVvHtAC5C3fZcuKtRrPjUfhQECyAY4gCxJSiAEUNhLGAwqb4LCAmCAOdRyiwowS4+XLygJkgcmUPCOph1ri2NW3ama4PyLC8XjHAiy74PiAxsqfsCgvHBR5ShAYmer0BhK4grxdyJdd9RxWULPtFWm4KI9aPeJ0NRY19fgx/Oxeh4Y6FhY4Z4xuaT2Hbc3itq24rWSmD4Y6DhsGCfwmvtfzHk0n0OjJlmWV2cXiwWOLUyiFQE5gQ+I5d/AoMumRHcdbBoeZ4/HmhyNDU8zvWu+tiAuJDAFkBykZjXktHMbdjozy7vFWEyyHeT/nxGideqXlpK0muN+CgVoCZ/bGDhdiYMt2BGqZ5aCZm8wy06t62slIfkcysXWqFM5oRo6No9/9K6FdyZlNd1PhVALiL5CBw23XLEkf1lhP4cbbqZYj41uYYztfJAcG3YNjmuZcz9hJA6pE2fVQmS3bU7yVASdaWGdzrdim28DG3RlxTlRz+A0ggQCuCs7sCNn1VkR3M1XOGIlzdsOBt51KwCqJkAM3UVoQIeu3lfJghfpOkdxqNhx+0zwzjR69wVlBVk7euK1+X7lIunfF3Bpv7FZ8NoxpEfJrb6IIbhp57qRtadCBgwX9wDmWQ4A5miPzvdV/uYyqgsAD5zwOHAbOAQTAnD/hZPwJpz8dNIyuYVpuggAAAABJRU5ErkJggg==" UUID="6e1a2bea-c6fd-4ec0-a800-8d65fd038674">
<layer>b0785b21-2e95-4eb0-8e4d-73f9748013fe</layer>
</tile>
<tile width="71" height="96" name="h1" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABVUlEQVR42u3dwW7CMBAEUP7/p1P1QIEKbBOoMmWeJV/IBT0FZ2dXIqfTZW32z75Zm3VZ10A0BkAkduOU4sF5Cef72nnDgbOGc41SCARnF849jDKgOc69XY0zgikCGuPMzqE6nNEdUnb3wBE84cDJepQX56u1A1kRqACEA+eobOVRvtXDzOucYpi1IrC4QFQhw4EDBw4cOHDgwLHgwIHz4TjBE9ZjcUbdxoBO5HE4s1ZsQKs2EyekwQ8HDpySp1XI3AzOvyoCg6atGfEhdD6fk60C5/NZwTNsPp+XyoPm85kti5Dvo58DBw4cOHDgwIEDx4IDB86fhtxunEl7pBdnobHWibPYku3DeaKZDwcOnH04jz5zII//VEARqEKGA+etB/HkGhw4vxAWr3XiyFavhc5unIWfW3eFPDmH4MBZyFdw4DwHJD7AETyPwAE0gPEKpwevcPoCv0IwQOUmIdwAAAAASUVORK5CYII=" UUID="2cc80226-f795-45e4-8c85-4f9acf7a1fb1">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h2" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABdklEQVR42u3d3Y7CIBAGUN//pd3szeqa8NNEmQ85JNy0F9STBoYxQ2+3R7vrf/1fu2uP9gxEowNE4hLO77XXDqcBcyBQH2d0zZwDZw4GDpg5nBUwvQm/eDGYW8pXwTyPF7BajpfyT03Io3AhIJyoW8p7P37Uvz4IhPONOGtGhvN2oGNSFlvi9JbVSqDyIHAm5qgAitg+VD1oYA4pB6c1dtTGs/oVD8oCZKZJQ9IjEuzR+ZztM4GHNn/qwYEDBw4cOHDgwNHgwIEDBw4cOHDgwIEDBw4cOHDgwIEDR4MDBw4cOHDgwIEDBw4cONcF4DRhOuUL5+JMFL5k4qys6eoA5eFUlku+jJuFs6LGa0ucVdWB2+GsrCu9UBpej1NRdD95qIDjGmIjZDhvhHF+zgKc+CAQTjjO4FomzqeDwtYxf1ss5aN7i/ZyeUHg7D35nNrd/5k4k2/j2TjR24cknC0ygUG5IxPydgn2GD84QxxAHRifcGp8wukHtX0FXbBvhWsAAAAASUVORK5CYII=" UUID="879ae6f3-950e-44a0-82c5-4752e957c1b7">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h3" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABjElEQVR42u3d0Q6CMAyFYd7/pWe8EblYV5Stp+u/hAs1QfKhrByqHMc5GstnuYzGOMc3EBoGEBJunPfj3gIOOGOc3mOOObVhxjiFYWyc4seb+zgzgKz1B+8cf50zYwOtHSAwW8bhjMoFgXLCN5Wvxhkt2xeBaXFWzFapcea/MziPA5WJLFLiWNNqJFB4EeipOSKAJE4fojZUMEPSwRE82dXCmX18S1Mhj4DIc3ZIAolJtT/imjiFBzjggAMOOOCAAw444DDAAWcHnC1aUFbApG1BWQUj34ISjUMjATjggKMCVH4qB+cHoJKnD2laUBSBOPFsCVpQlICILDpARBbkOeCAAw4DHHDAAQcccMABZxMBcLowRjxSF8cRrNXMc5yRbL0k8EaYXy9DTomz6upDOpyV161u/DS85hVP558KcK1ctkIG50EYmpcW4UhXyJE48hWyAs7guZp9yM711OxgT4FjVKfD12btFPKcBs6/XzFiUjJkB0yKJFAoOwIHnIyRRRIcgAwYbuHUuYXTCwCvznfeB53yAAAAAElFTkSuQmCC" UUID="d110f5d3-a456-4a14-a48a-33d03703e205">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h4" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABlElEQVR42u3dwW7DIAwA0P7/T3faYet2iAOpDaQ8pJwa1eipRMaVyePxGk/X7/VvPI3X+AtEIwAicRnn+/MNAc9xfmDgBDBwAhg4B0sJTvCMgRMsp5blFd2TuTxHxUnDie7rAe6FqYqTluecIfb+AnthsuMMw7myPK/gZMa5nAT2LCk4u+OsMulb4lyd+Mpx4GRvH3qeTUUTHhanKQnsyTwHTHhknLztQ2uylrfxKY+TjzOy3FEcpwZnZAWxMM75A/nd3GRMbUElcN0a8oajbW+16fCnHhw4cODAgQMHDhwDDhw4cODAgQMHDhw4cODAgQMHDhw4cAw4cODAgQMHDhw4cODAgdMvAOcQJmhf2BenofGlFmfVlqLGlqk6nFWb0Tqa7WpwVm5jnIqzegPsNJw7tE53tIbn4dyp6b7xUAHHNZRnyHASJ+z8nIVxGucBB07wfdOWVWZSmB1nCE40sbPPZsSZkiHf5fjN6RvPO9Vzgt09nPLtg0rgh+OoIfdVEOGU5zkf6wfnFAdQAOMVTgevcPoCrlKTpJyZzuMAAAAASUVORK5CYII=" UUID="956d7658-3fc6-4422-84ae-2c0027e416e2">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h5" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABw0lEQVR42u3d3Q7CIAyGYe//pjWe+JM4Wrb2o9iXZAfqQcnjwNKNebu9253jdXy1O+3dPoHQGAAh4cZ5vj46wAHHxmFYgRMzrMBhzpn/KbdwRoiRwKo4YTijsyzyDFTFmZ6Qj4JZQzBqiKriyHCsIwonMk7ohNwO56gzKztdCqdap7fEOdvxynHAiZiQf72+2nHVFxGeBHpyiCsdj8nrJXFsnNnAqnWZIE48zkwakAGUuvCMOmVVK/nEOLklC1WJIynO9Qy5bT2nMYwvCWxcS+aiHjjggAMOOOCAAw44NHDAAeePcYS3k+yFI76dZB+cBbeT/AeO4roXOOCA0wvnLFD7n3JwTgC1XD4Uhamztiq6naDOwrPgtflaq/Ji1+brlSwK9YV6DjjggAMOOOCAAw44NHDAASd1odsbxyiR9MVxFNdycapuKXKWZfNwqm5Gmyjo5+BU3sa4FKf6BthlODtsnZ7YGh6Hs9Ome+dDBXhcQ3qGDE5gh3l+TnEcSYa8I44sQ94Zx3gvFycyKYyMI8MZdcz6bFUcZx/ykkDvZyviyHF2r+VISxa7AoHjG2rggMOcA47WEBwTB6ABDH/hdPAXTg8nXljRpaVtlwAAAABJRU5ErkJggg==" UUID="1be5f51f-7a2a-4d0b-8100-ad3343f796f5">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h6" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABwklEQVR42u3dwY7CMAyEYd7/pYv2AqxE44SOJ0n9R+oFDrY+NWwyDcvj8R4H1+v6Nw7Ge3wCodEAQmIY5+/1zwucExhwvsAwrU5w+MwJ7hymVSdOMaD+aRXhtBCVwK46MpzWXaa8A111ZDjRFFRNUVcdG050qXCUdaQfyOVwRlbIJXFWa3pLnF8bX7kOOKqNZ08xU8O2Ol9xetYQVxrXrOstdWKc0cKufZmhjh7HGZQl18nBcQZmiXVyIwtXxJFUhwz553VO8Sw5XgQWzpJ5qAcOOOCAAw444IADDgMccMDZBKfMEZSrDd/6CIqi4dseQVE1zUECcMABx/nXiiMo4Ggb36GOfPtw+yMomY1rNz4TjqBkNZ6zMzQfQcloPHfrbD6Comzcky2Q5xB2gQMOOOCAwwAHHHDAAQecZQXAOYVpxB11cTqCspp5TmfEWi8JHAjn62XIU3FWf/owDWeH51YDXw2v+cSz858K8Kw8fYUMjrBhDi8tjmNZIe+IY1sh74wTvFbzHLINp9VY9N6sOlacxiozfG9GHTvO7lkOOJ1TlJj0YFpdCcyYVtMy5J1w0nblt/UDJ8QBqAHDTzid/ITTE5cWIforoVuGAAAAAElFTkSuQmCC" UUID="1fbbdf52-331a-47f7-8d1e-e84398cefda8">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h7" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAB0klEQVR42u3d0W6DMAyF4b7/SzPtZl2lQZJhn/jgPxIXraY5+qZBcjDt6/UeB8fP8TEOxnv8BkLjAgiJaZzv12cHOOCMcUbvcc7pCQPObZymMLE4VyfuyJO6qk4YztWVLfKqp6ozhXMHZjTp1Ymr6shwRkcUTmQdcMBJxqk0aUuc/068ch1wInblf72+O3HVHyJ8ETizhrgz8Zh1vaTOGGe1sCoLEtSJxzn7HTk7w9Q6OTiz56loIKuYVBV1JNU5v5Qr/jWE8UPe9kEBkxw/5OU5KpjE+GHfCrnqfgwccMABZ9MOG5zH4IjjBz8cZcxhiVNo0VcTRxlzWOIoYw5LHGXMYYlzHA/EadmCUimbKdWCUimbKdeCUmkTSSMBOOCAo44faEF5Go4ym9naguKQzWxrQXHJZra0oDhlM/IWFLdsRt6C4pbNyFtQGOCAAw444IADDjjggMMAB5yATWtvnEHc0RdnIijrmedMRqz9ksCFcL5fhrwVp/rdh204DvetFh4N73nHc/JDBbhXnr5CBidwwjQvmeCkbh/AASdnMVa9Dzl9V+7awT74md7PPshwnDMdcNZgwAFnEkaaBLrlx+CAE2gIzhAHoAsYvsLp5CucvgBHwDbXJzzzzwAAAABJRU5ErkJggg==" UUID="53fe97cb-710f-4630-9c1c-8cdc03c77212">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h8" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABzElEQVR42u2dwW4CMQwF+f+fpuqllKrECTy/OOuJlAMbJFsDeJ1ZYG+3x7gzf+bTuDMe4zcgaAwAQWIJzvexvxM4L8A0BDSGEx0DDnCAQ0HeVZBHz1NCdsWRfaxGIJVnPVccGZzoXaZqC1xxbHCiqYKjjCOtOe3grJytWsKplvSRcN5NvHIc4CgK8n+PP03c9ULIm8CZHuKTxDV9vSVODGc1sMsFGeLo4Tg3rclxcuDM1ik1oKM0qUtzJMXB57zd5zQGM9cENr6mxUU94ACnMJw2gl3VykdrVeNYZJe6oSwh2DGBwAEOcBDsCPbrwVlN/IQ48u3D5QV7ZuJZetQm2LMSz9KjVsGekXju1tks2JWJe9wCPgfZBRzgAIeCzKmcJpDtAxvPahtPlAWyCzgyOAh24AAHOFXgKJu1I79IMEosWqsaJ70JnF2rGgefAxzgAMdsEXvDCYp3XzgTp/2egn2yYewn2Be2G/0E+1Y41QX7NjgnCPaFn4b3FOyTfyqAQ96mLBDsmMDrwbF0yCfCsXXIJ8MJjvUU7DY4o8SitV1xrHAGXWa4tiPO9l05PudwMLaPFXCA07PeAIeCLOQHnBAOgAZguIXTi1s4fQEyvKBRH1NBJQAAAABJRU5ErkJggg==" UUID="298a41cf-2be4-422a-9b59-f1aadc41f48e">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h9" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAB+0lEQVR42u2d207DQAwF8/8/HdSXFpCyl9Q+tuOJlJc06LgD7HonCz2Oz3Fyvs8/x8nxOX4DgsYAECS24Lyu/T+BcwGmIaAxnNk14AAHOMCJGpBH91kO6qqc7an8KnB0r+Wsp8rZbgKvwmY/ZVZtgSpnG84oaFTU7LSCY5lj2iED54gvOhWcbEWXhHO38Mw5wLFoAmez1Z3CVd8I8yZwpYf4pnCbvl6SM4ezG6xyQYIcezhKi+ic4wNndZyyBlRKk6oUh1MOgv12n9MYzFoT2PiZFg/1gAOcxHBaCXaLVn72WtYcieyybihTCnZMIHCAAxwEO4K9PpzdwivkmC8fHi/YPQv30qMywe5VuMc6SS7YPQB5rpPkgt0SkGKdJBfsvqJE3tABBzjAaQDHervcnZxHTeV3ZqpSU7nHTtRHNIEWb/Dbr0sJR/VGyy08Vb8i5ZSFanBtbwJV0z+CnacPwAFOKziWzVrJjQSWyjNLjnsTuCvLs+WcbHsDDnCAo7WVveFMBu++cBam/TjBHpmz2DDGCfaonI3lRqxgj8gJhZN9f04YnAo7uzb+NLznnsDFfyrAbtIwZdEeDibwoXDcO+SqcCTLh4pwFmvoKdgX83sK9sV7ewr28FV5BZczuQYc4AAHOEoXhGAPE+zl+QFnCgdAAzB8hNPFRzj9AJX3fWZtiy2wAAAAAElFTkSuQmCC" UUID="db19c7f8-44d0-4e03-a848-747a3a31da9c">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h10" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAACDElEQVR42u2d247CMAxE+f+fZrUvy4LIrXbGTn0iVeKm2hxCMp4m8Hi82pPj73hrT9qr/QcEjQ4gSEzB+b397f7n4+XhfIIpBmgOTqtXAQc4wAFOFJzeoO05oKviuMHpzWqeM54qjhucVlKjpFcTV8WRwRkdXnA847iKwLJwZga5UnC8BkngGBLPHAc4V+H0pkmPxFUfxDYR2Ju1LIl7qF0BmDk4q4GvgvGUENtrK0tw6xuzAJYUntZPRqWNNjqU4wHZqk0UmmWTjdKeynd22QBB569zdiV1Czgbu2x2MH4KGTjBgFJ4yBkBpTHYI4zvq7pGbrBHGd9XXyM12D2/FpZekdJgj9YsKlWNTQoc4GCwY7BXg1PGYD+hFAgz2HcmrqrJUheequJRbrDvSFxV1R9lWajsBrnBTgMOcIBzGhwGZKZyRCDlA4UnlkX+61aYXcDxTZirD8ABjjTpMpeDV8Xa7RYS9BIbPbdaChy3BKWV+Oxzlt51xOKlE0qO4+CwYDKH2gWOs4sYAyfL8v7BeevCmTh3jMEevaVo8vwxBnvkZrSFGDEGe+Q2xlA42TfAhsE5Yev0wtbwmgb75I8K4CFjsGOT4iG7a6PODyvVgzMjIcrD+XZfCsfT+PZW1RI4SuPb08iXwVEa39Y4IXBOacABDnCAk8G/Bo66fLg1Q+AM4QCoA4a/cGr8hdMPbXLy1LxJTPYAAAAASUVORK5CYII=" UUID="934f6d8b-bbbe-43b1-a28d-913c5c80fef9">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h11" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAEgUlEQVR42u2d23LDIAxE9f8/TacPTWOs1a4AA2nJjCfTtI7Nse4gavb7Kud4HZdXOa/f1zugQyMAdEik4Xx//n4cOAeOrlb/FMyBc+AcOAfOR8D55LSgD04NA8BRv/xyvvdzfY3gb3uD1Wlw3s+JngwEpMLxwFzir/qCBcZm4+AEAeDPgGtA33cmAXq/lqfG1c8X8O79ATDVNfvhCJFxBMcDZAi4cHAwQG2d34+BI9qSDJwhhrQD8lQ43s1T6Xl9jt6JEV0OJ9Lj+obr98D2ZODcge8AR9FjAgd5MwWMB+UKpxVQeQAOeQLeAJAktKiU69DSgEa58gScjM3pgdMvPcPgFF08s668A0677XkqQmbB1INwrtJY/OhblPKxcEp5JkJOGGRTo9oEmLFxTiJCRrnV9fNWg2xsBHKte2wQGD0BUwZ1VbUMnFSZQUw0l6UPCEwEZIjkOA/y9d2uN90EjgqJqXC2aGVO8rqt5CBIRjJx7zzkEfXkeDEcz+aoYIr71DVVvEnXznCwSuiQ6nPiWGpzybkM+FLixAeD6NkvZdC2i0GGA7xl3O1HXfhCQaMJCfIUOAaK3wySCiqqBhoBtF5yUFRKIBW1PKpAIfbFlpdJOyDRdwJFjYvmSQ4S/QZITHIyDyICMh0OzKESkIpZ6Iqz3xmXayfBkdytOqD6aTdB8T3VUptzA9WoCsVLGEUoUVoxbiFBw4Q8AiOrG7AxFGbBkbOBevc0yYlsRBS3MECG5tXFaHsbyTEQ3KVimAoQAxelG1tKDiuyhzWX6AkLSa5Sw54Ox89dcNHcnLpzIa68gPPQnNk2ksNnGvzClwsJQMeD9K+zleQg6YngwAwalDb9c2I4W0jOp8ApR60AyMS083TJmWmQ3US4LIKTcakzXPktGgcqvVxyVgWBCMw2krMifWBgtpGc+MZ5Zs4AhYltMKuxleTMKFlwVZpskONpkHXFLgRmOpwwSFtYJkVl3D1KFgsL7IV5sdlqFRo9NsDEFE0vpOlwoDQloGQn91oglR0kB7r7YC1z67Qwg8Ts2hTJYRn0zAUFcEXqSsnRvRpe8cWWocBo+zYrEcztT/dWJi4mYpGzsHippbl1Czhqth7NtZsASVnptbXkKGkFq/kyMGp6sM4gBwUrZdZCWVcYLdWVu/1WGeSCilQNU8tx8UyBtJFauQNM7LnDDWq8+n2IzYkXXPbBgWAEQMpaZtYiEDekEDh89fwgOMkWHxQL+cV6vaHE7RRUe7YegZNsDlOMc0sTG2yjDLd8wE13/XAyfZeiIX4UTqJdsw/Ogw2p2V50dRFDstF3DZwRBpnaGq/EMgVOZxM8y+xbdzG4uWgGB3zWCadv+wRW7mixN24gGm794GjAEIM8EM4TxnhpmXTExhsj9+BKufKlcITfZzr2cBdfJ5xHg8DGbaLCKV+W+5BmuK4IeXwQmN9gzAZscsa6BSVvFmzeNj7xVM8ftD1expZMg/MnNlEUkuL/u5P2gXPgHDgHzoGzERiyU9OBc+C0BasHDoFzAAVgzr9wAv/C6QtRQHHXUh7xYwAAAABJRU5ErkJggg==" UUID="7c588781-b5d9-493c-86dc-6b383cc33098">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h12" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAFC0lEQVR42u2djW7bMAyE+f4vzWEY1jg273iUZMlpFCAYgq5w9PnEf7lmr5fv98/77eX79XodAW0aBNAmIcP5+/n83nAAmC8FhOFEStpwNpwNZ8PZcJ4F55PTgilwGi7y9rsGYP+7zvU9IlB9HBwG5vjvGY4ZAfMWf50v6DA2GweHBIHqRSJJR/LOlJN/PwBmjOJr6UMFznFxRr4oUk7pxiU/HwMHgSrAeSkEG8NQUSf1yHCy9y1wGm3Oj80QvMRl+30UnKLVP6rGgTulnxGg5XCYkSvA+Q/Gi2AiQC84rYBGGGTFyCUXOavGgXdCajwDujoLbwIzHk6DPM9gojhFhRNe55vhaIDqYAbA8e69m8Fhi6Y2qAXQbelDQ6QZgankRZYktyVAt+RWHREyUg3zVCia7gJ0W+JJIuMqnHOUzFIHpDKp9CA2AcYGgUV5XqNiDAiByRJTCEmO2hdFyFcI54VEizP4/8oKejqc14I9BBKVIxCYDJCUyD4BznVxRxugljDx72hxEi+ULYFjsEBl1K6cf88KMFNP+QTlIJsSKchYlY8C1NX7ut5i5RgohscKCkBZbH8im4HqzrEtim3XNDjIkDIvVDHIbEF22Trc69n8Ypemmjzi5QuCdWZqs1zyflOUo7pk5qXYnY6gZEaaba/lyuE2Abv/0NYIauHQfb5yokVZknCi8gRTzQVM4K4t+LzQlSteysMvz9SDVBOWQ8gNWAqHb4lYOaiuzGxUphqlD/9I5URbJwKE7FGPapbCybwUsilHQMzjZKpR4CI7t1Q5DI6Fdx6AjuAA5cGczxbFOZcFUy8VuOIkCTWgHLVJGNmnZcoxaijxQtUiGAoGYTvZeJY+NULmcrfQMIf2CCjSQZs48m7+FOUwCWcG+VLaQJ0LksUzOFkN+zY4SMaxLYhduRmH8wbfeR6mphpzDHKyt1EA6MCuRHWgCEwlaBw7TaoqJ9nblYw6Lbaf1WQ46XRQN1qqnJbBJLM43gntjcdw2BZWakT3lklJghcaSg9UIKjJjS/YiGKmKycL0zMvgtw/rM2oBllIaabCwTWa6whtFU6ovAbVLFNO1iEwUTVR0Bg6Ace2Ji/ELWjqjVYNK9DTcOFJyglrKczeCKrJ2irMSymF+8nbCtdmlFSjWnCnmf9TlNM0dATiIqVwz8uiJraMJsMxUBOO1JKPj+QLRGp7nHKyGRxL6ilqnJK1kKtQJyknGzHRT+pBJVg8jREOKghtoukG+ZIWCCMjbEKiMraitIbWDi8Zn1pnRhqNyynb6DjslNmYpcqxZDCbHl/0qxpUmC7GNI+KkPn2yb2fYpDRuJzyvafAqRbkswl2tQjGplefDUc8pM+3GR+ODCuGLXC4sxgMh5yZaBk6UtIWb4WTHGMYC0c82kN75J6pynHnogJHOAAzDk7x7BMHVOyfVeGIR6fGwGk8HMYSSqacsOWT5E8th+764VTOXYouXo2T2uDoxzX74HQcSM1qNF8Px5IRNjltEap9LUfE2+EMOQSfdUOLTUL5pmoPF+iAM+LxCX1PL/BCjXhuhNwJJwJUKU5ZQTVQOZ8Ex4WHEWUtnNq2sgVwCoHWeYApc+2s+VcLQe6OkLsfExWXRFlimpVA2r/3HRFy1wPGcuVkD0LruqnROmbWc9K8SFBOL5ipcD7+IYp3wfkVrw1nw9lwNpwN52FwRgeBvxpQb/rwPew2nBTOBkTA7D/hBP6E0x+pfPNHIRXstAAAAABJRU5ErkJggg==" UUID="0d75364f-a431-41dc-9a23-75b0e200ffce">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="h13" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAFDklEQVR42u2di5KbMAxF/f8/7c62013Aug8ZYycbZ4ZJkzQBDpKsJ1vKz6Pu7Xs7Pep+/DyOgDYNAmiTkHC+no8bem/D2XBatbpC+EAoHpwPBsPhfDgYDWevVltyhtqcdw4LHl+t7jqN/75fvp//b9nX7WfOfkfCCd4r6PO/23UPsR07X83S/doFcx+O6QRSG3UEVMDnFwnKbMfvXFVFqc5cOCEY8l0EyNmEKl2hPaNWGfF0T0zAsQxpsDE4kRS9JRzzII2ViKvZOIMc2Y2FcNhzuwLG7022Ob2AsESo96/PR6m5qtSzcMAVP0ta7QbTniB+nYUTSeZbwlFGU6kVW6kGwqmpE+sCVMfDUUb7AYOcNKIJMKPghP+Wy/3dpdw8sRQgEwzych3VUtIzzs8haQu6EwGGwXHjIgSmiuexTmCvG27kg9AqxE6CSg/ZyiNw7gRwRGUyhjJjjOsUmzMw9L/GRwhMEXbJMeDRvo6S/BJwVABZDEhK1ZihjQLW5WoFr1wg+pEHrJZrxyZFF2CpzQlPkF1tomI4bECBZbBPkEGcCocGhkDsEaA2/VmbuAkFkeWQECvG7z4O53TQHYBa6YnzwyqJrsD05JoHSE7sSyjV4uqFwaCrfzwWpVLz4NCrVFPSE6mBU35BYMol77wEDluqI4jI5a9JMKHqgmNiUve4zUFJcAaISY9SJ9e4l6ByscbmAEgo9oEBI5KYhFsAQ4m6wOacTkakC5CPkzHCCkpzbEtWqxJfYZzoHtOEUJUHTAqCc9Wqeo6XXC0SOWir8FdbVZ3sIWOpuda1UTKtt4UkKzEvAacQjzdtywwn0pGYNX7OXclhuWPDP2qcPSEx62IrwyD3qor6P8wdYCmUx+Gg4DDTetazjMf+lAazJHzAK5MXH5USOXwsCaYbnJhTuizw9DqzSIeEyMEoKYN5nOk2R4DhEPNq5TqRVMVWROVqaVchRcZQSyjEOE81yCWIryK/g5Zogy4O52RQyy4DtGy1ivwOZ7VyHD8HjrO0L/FzsnFQV8D5TpKTlRgaBxndGby0/EKS0yMxoyRK2j4g2VPh2DZGFvF0alQlygpIrl+PbUlppiagtJUJnh510q2wirEkTVq5xFSjq0KBcRLzDNxLVDxpLFN4yEBVCkmPAKRKRvP9nOhAiijGVR1NsyYk1s2xNPCEtWgDjDbCfn+N0+6yxObIpBPtDOVgECAlPY3avVLFE4FB+RhUv8oad5VSZU1XjweeBRhhZohRo5JTxLNVizRdTbM5TjqSXUlaC4c9PSyFgpuupiW7atGTcSppFcFRXaI9PX8LauXYv4gOKNtCi1SsJ7s3vWGyiLJtFgrbD5WSKXPlWSdQ1LUzQJiq2fMWPXD48dxLsKtBDDUK5F5xe5QpA0cMsAxptc3MZaIDdQy73Z3hwDFGn4bbHDVjqQDdBuOMQvkz7/fguOODw+Ak50mb306Ma96XHBA39aoWhdM5cHv+DX/QdxgcludBcDIj1G8FBw9f5OC4Q/cjhvxDOOC9YXDiWlK/A5g2oul5eO+2FEMMspPr6ZlFeAzOjPABpU0zIYYN8B3hsJWK5ozN1lrbN3HhzLA5PEtISsddt6JigCo32LW+BhyWa75z4zIM6LrT6t1Jwbxx2yNw9HhQ/vWYW/BhGMPg/JqbKJKbRu47aSMbtOHwvM6GsyXHALNtTn1+tfqVajTKCdxw9mPDceBsQATM/hNO4E84/QG9hpOZto9YbQAAAABJRU5ErkJggg==" UUID="a3397bc9-187c-461a-b336-7059f6b8eadc">
<layer>06be52cc-1d25-46cf-815f-2f47f0d4ae26</layer>
</tile>
<tile width="71" height="96" name="s1" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAACAElEQVR42u3d24rDMAxF0f7/T6dv03RIHFmWreNqBwKFdgxZo+jiBvp6fY6D8+/8Og6Oz3EGQqMBhIQHpzIcOF6cc/YGBxwbzhmlKhA4vThXGBWBmjhXZ2kcy9xRHqdVvUritCKkYvSAw+AJDjhM5QyePzBbVQZifGB8AKcGjso/4HE/Z/WGl1KESpVytVtYBkcxx0mMD1cYCkDpOHcwCkCpOE8Rkw2UhmO5lbKBUnCsMNlAy3GsF64AtBSnB0EBaBmOJzqygZbg9MLcvV4NNB3HC6MANBVnJGIUgKbhROSY7Bw0Bcdbmp+q1WqgcJyeBq8HwLOWNI5lZ/Hub6LWksHx9C0WEGskRUfPNBzLBUW9nhU9qTgjkbMVjjW8I3NOz5qyOFFVyZt7tsOxVB9vn7M1zkj18XbWW+Qcazm3lPGfyDneRjByvS1xrJ8dWVMKx9vuR3w+skue2gQ+5YuRccC6ruxsZfkO3nJhEetKbln0Pu/TU+FmP0e0ZLPLAzMKtN0ectRtsepxFZkn2Hk+B5w5Oaokzuoku+VtpXiAAw44IbMgOP8AiJyLiLnabSyN09MygAMOOOFzXNlqZRlP6HPoc+hzGB/AASe3OlneAwec43a4bL1XEocOeXB0KI1jud3Y7GJ8ACd06AQHHN9EDg44DJ5TcQBqwPATTjc/4fQGfFZGq3GbXZcAAAAASUVORK5CYII=" UUID="a4e9de58-b1c8-4a46-8b60-71c4c6a463c9">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s2" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABgElEQVR42u3dQW6DMBAFUO5/abpqkyIwplXsb82zhBKxSOBB7JlRbLbt1Xbbz/ar7dqrvQPRaACReIJz9juEcwFTEaiJc7dPnwNn64KBA6YPZwZM0kXoGsoHH0wM0O1QPqpDTgwbIoby1LhqehDYkyGXxHlSQiiVeP6lxgKnOs5/KnRxETKcgUHZ8jifPLDWib+/RuKMuHJXn3sWlcd0yDNv7fjcahbOErlVcjgfl3geO0jFLlVAOLG5FRw4cOBocODAgQMHDhw4cODAgQMHDhw4cODAgaPBgQMHDhw4cODAgQMHDhw4cOBEnPx+81djOHAOKN/vj/vK4jyZ+BKDM3PqUjxOTx9QBmfmNMbeuWRR88rPOsiRQBGjVcrU6cihPGleeceSXJZriAoCLfQBZwzQqO+NSh9mdcJL4Dw52E8knh376uRWZxDRd87I3OrJd8aVLJJWtCxbCey5S0tXAjuXOq5bJoWz+lCeGFfpkFcaygMB4bRwADVgPMLp4hFOX8SGds8OOd5qAAAAAElFTkSuQmCC" UUID="0db5fd69-a18f-4024-8a6b-b33b1514ebac">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s3" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABm0lEQVR42u3d0c6CMAyGYe//pvFIQQJbh1n3Nd+7xPjrAYFH2dqu+X299rHx+D5+xsbYxxEIjQYQElGc1r0IDjh9nLvXzDnmMF0cZ5gmjvt8M4yTAaT0IYTjnAwctW+pDI7ibRxaymfjqMZVy4PASIZsuVqNlBCsEs8nNRZw3HH+qdDJ5VbgJAZl5XFmnljrwo/PkjgZn9zdca/qSDIT8sqvtnxutQqnRG6lHM7LJZ7nCZJil+gcAE4VHPcBDjjggAMOOOCAAw44DHCq4pRsQUk8mXotKJkw0i0oCjCyLSgqMLItKCowtKDQZQHONBjJFhRwwNHHOaYPtjhXF3+VW9muVuRW5FbUc8BhgAMOOOCAAw444IDjcvFbp9UYHHBOKJ+/z+/Z4oyUYu3qOSVxInOADc7KGnJ0d8N29yFybOt9K8mlXGlTL/DP3NjxlAoC6bIAJwdoRdBp2RMoHec8PdkZiWfgPa+98ujxLbssSuL0grKM9IVKYJXVSqkqCM7A3GN/W5VYyhXjKnDAKZh4VsEBqAHDTzjd/ITTG6IK3Uyd9KUDAAAAAElFTkSuQmCC" UUID="7abd8566-d1ef-40a6-95e3-7079a77155df">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s4" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABlElEQVR42u3d4QqDMAxG0b3/S7tfzk1s1tR+aWNvQRhDiDussSlEX69jbByf42dsjGN8A6FhACHRirPPQ3AKMOAYMOAYMOAUphI4Ro4Bx5hOrdMrClQVR4YTlauUcSTrnKhkro7THafHdJwljmsRWIuovvDgOPdxPNsBapheQF0Kz5b9kpnjgBOBc2e3bcY47pwDTuMCa0mc2iDWBZ0LVwVO7ziuqrwF6GpKKhKyKg611Sicx9ZWUUvz9DuBV8mOPeTgvAHO03BWH+CAAw444IADDjjggMMABxxwwAEHHHDAAQcccMABBxxwwAEHHHDAYYADDjjggAMOOOCAs8CP/9fwAg44J5T98/m7ZXE8jS8SnJlbiobj1MznUXHCcbK1Mdb2ksn6yq+S3UwNsDXnLd10L7+VZ+8rr8DjcQ2yRSAP+gAnBmh0HOd5KR8XdSsJh+N4Flij4zgQ16mthuNQW1VeSNQ2RJoti2wbXqV/IDjG1FweR1qVPwUn9G6VCSf8Vp4tIYMzqvB8Mg5ABgyvcCq8wukNp0k93oxQdtgAAAAASUVORK5CYII=" UUID="646ab72c-9789-4271-8fa9-e8d416c0dde2">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s5" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABvUlEQVR42u3dW47CMAxA0e5/0+ULKChNHTd+pL6R0CA0kpkj4sTupGzbd+w8Po+fsTO+4wiERgcICQlObx6CA841DgOcOdMKHHLO+FKuxfECtYpjhuP1ibOMI0rIo8G9pqR1nOk4XjnLI87UhCypdq1hDOJstz+uI+0Aa5hZQFMKT02/JHMccDxw7nTbMsYZwpHugcrhSAKUxJEG6b2h408rnNlxVEv5CFBrSlokZKs4qk2gdqf8uNpq9k75UbUVfZ2LnNNKdjS7nPMGOE9tk1Yd4IADDjjggAMOOOCAk+MNgbNALZcGJ2PfKAVO1sZaOE7mzmMojuc/BSyF433dG5wn4ERc2gUHnKI4x/KhLE7rj2/VVmVXK2oraiv6OeAwwAEHHHDAAQcccMABBxxwLjsB4IAjR3k//3+tLM5IE98EJ/ORonAcyXyOiuOOs9oxRul1MbNz5a1kl+kArOT3Sh+6N1/KVz9XLsDjdg1mm0Bu9AGOD1CGOC6rlXaDFRnHbZ+jDRwZR3rTtnK1VThO5trqDMK9n5P19pspcFbs6dAmFRapXH2ISMjgkHPA2Rks5SocgDowfIXTyVc4vQC91J5h/Xc0IwAAAABJRU5ErkJggg==" UUID="635bb570-ae3b-4789-b519-1e2bde2efd2f">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s6" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABxUlEQVR42u3dYY+DIAyAYf//n3afNh0RKdoWWl6S5ZbLcmVPhFHozm072s7j9/hrO+1oZyA0boCQ6MUpxyE4FRhwLmAYVhUc5pzGlcOwEuKsBiQeVk9wvDCt4pjheF1tlnFMcLyGo3UcdRyv+cojjuqELMl2rWEM4rxfIfdsB1jDaAGpJJ5P9ktmjgOOB86b3bYZ43ThaM41qXAkAZbEkQa569D5pxWOdhz1dU7tNeVz7QnZKk4VR+syTZlbaeCkza28luYp9nPKyW75zS7veQOcbDirN3DAAQcccMABBxxwwNH/415vIhRO2kICzQ6nKyTQ7nCqQgKLDqcqJLDoMIUEnJWDY9bhNIUE4IDji3Ne1qcqJHgLdJXzpCgkILcit2I/BxxwwAEHHHDAAQcccMABBxxwZnjzrS+8gANOgfJ9Xv5uWZyeDfnldgKH40jG86g47jjRTh+kZ1zLnltJXrf0iaf5R3n042ABHmflZotA6nPA8QGKEEd9ERipmtTlo/zpAmtkHLdFYLTcqpGB+2TlM9fnDMdpLbBGxpkCJ8JmFzgzTciRgcDpG2r8r3VwRq+Qo+KY51ZJAbnfzB0Mt3Cq3MLpA3CnBO2biHJKAAAAAElFTkSuQmCC" UUID="8bb9387c-1e83-47b5-916c-d606cd68dec3">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s7" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAB10lEQVR42u3d7W7CMAyFYe7/ptmvDajW1KH+OInfSAiEkDw9oml8Eo3H4zWePP4eH+PJeI13IDQGQEhYcUbXIjjgXONcvcec0xQGnLs4XWHCcbJQo+qE4WTd4SLrDHE8YCKBouu442StkTLquOJYut1omIA693Fm4oBoGC8gl8bzm7xEuQ44GTh30jbFOtPrHHC+XGC1xLEWGf1B789RON513MOus88cX3tPyFF1TnG8vqZb9lYeONv2VllL8y0y5ONkR9iVPG9UBGTL4ahdujI7nopzmwSO6uRfjqN8dyzFyQyulsLJzmbA2QGnIn4AB5ymOJHB1dITcnRwtXT70K63uruVXN3Jh+FsfwRFvcMuPYKi3GGXH0FR7bAljqAodtgcQRFpVsGJxGl9BAUccHJxvDtsmSMoqh12+REUeit6q/XyHLUBDjjggAMOOOCAAw44DHCMTSs44PyP8vv6+F5bnJlYtV2eU45juZ6r6qTjrJYhW3cq2u4+WD7Xet8q/Fa++qaeAY8dz7BFIKcswMkBUqljnLB7nglMxZlZYCnUScdR763KcZR7KytMeGSh/O83y3FWCbzAmYQBB5xrmLDIYqcMGRxwXCHBGeEANIDhJ5xOfsLpB4q73PjwVX6PAAAAAElFTkSuQmCC" UUID="481c9b0a-76b8-43d0-add7-e76075b61a9b">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s8" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAByUlEQVR42u2d2w6CMBBE+f+fxjdRQksLe+vuaUI0xjj1hG5nhyjbdoyd43v8jZ1xjF9A0OgAgsQMnKt1CJwGmIqAunDuXgMOcIBDQY5UkK2AaumoLSurM05TRwWO1ZLU1hGHY+WRLHREa85It6sNRkHn/W41Ewdog5ECJNJ4PslLIusAxwLOm7Qtos4UHMlakwrOiEBJOKMivQn9PmrBkdYR9zmt95yfSxdkLZ0mHKnTNGVvJQEnbW9lZc1TJIHnYkcSaFw3gJMNTvUBHC84KQP26ObMzQSuYM7cTKDGhFMF7JrB14I6m8mECdjJkIETKr4MGbADBzi2cK6yoBQB+1tAVz1PioCd3oreijwHOMABTg04FGS2ckwg7QONJ3CAswwcYlLg7GaAVtARN4Fc1Hsxce3WIfTlYHoreiu5D7eKFshzCLuAE+nL3/3gBTjAOUG5yoJKw5lx1+VMoDucyCbQHM5qAftoX1Y2YB95X+mYVH0rXz1DHoBHwK5mAsmQgWMDKIKOyW711GB56pj5nKfCnjqjf9hWMmB3hRO5twoB585geeq41ZyVcx2SwCjLCjjAqZMjU5ApyGIAgdODA6AOGG7h1LiF0weYebQFQ6Ox1AAAAABJRU5ErkJggg==" UUID="6d452f50-9a23-43ed-afa2-0dfaf51b9847">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s9" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAB9klEQVR42u2dW66DMAxEu/9N9/61vQhCTGzHjxMJtQKkMUcltodUvF7f8Wb7bP/Gm/Edv4CgMQAECQmcs/sQOBdgOgIawrnbBxzgAAc4kSZkL5hWOqJULgnCK8NZ6kwXgZIAvEoAa50pOBJxrxrJQ0e1Qp7pdq3BGOjcw9EIWCNwLx21xvOJXxJZBzgecFbctog6IjiSTNYKzoxASzizIqOAfj+t4GjrqPdWV+ccv2tPyFY6l3C0fqYleysNOGV7K6/SvITZdZzs2ptd3vMGcKrB6T5M4ZT1kCPXH+E85Ej1RygPOZq366WjCqeNhxzV28VDrmKTAgebNAYcqePYBs7ssfRwzuyOUdBPjm/xkFcBnd0iTy787jypTrreavW2KNtbaUzeqXqrqDULcCrA2ZGWgQOcpnAsC7rUE7J1QZfeQ145P1Wd43Wh4SvkNutzok6uKbtynEDgAMdlQubpA3BsAs+go14E8qx8IXCvzr5kbxVNh5VduywL1gQmHpOPfoADnAOUs46+NRxJjdQulW+HEzmVu8PJZpMKl7z0s0kFK816ml3mqTy7EzgBD5sUJxA42KQm/3/HJvVM5RltUuEy3l42qRBiL5tU+F+MXjapW2+V3eya2Acc4AAHOC41UttHM9tTeRGAwBnBAdAADK9wuniF0x+/YThzVf/fQQAAAABJRU5ErkJggg==" UUID="7e1e4dc6-176a-4a3f-82c8-1079d5662375">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s10" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAACEUlEQVR42u2dwXLDIAxE8/8/7ZzapB5MAUkrYT9mcmjKjPCLC7traF6vTzt4/b7+tIP2ad+AoNEBBIkROD9/c+efz+8/Hs7VZAWczl0EHOAABzgV4ahARtUJg6Na3SLrhMBRLf/Rddzh9MxcNBjvOq4icMTtRoMJqGO3DzNxQDQYL0AuxnMlL6lcBzgKOJa0rWKdKTiW1enWcLwn4pFBj4wlHc7MxVz1nZUEo7+z1jHBWfm0W31mJMEsvNU6LjrHepvO9LfUSvFWXgOOVNVp3kroXaQ13eac1mSXASYLkHkpB44wbqgEKOWJJ3CAsxZ8e6tdWcCuCr691K5UBGYG35b+EhGYFXx79A8P2DcKvvUBe3UrQMBOwE7Afu+AHTjA0cLxDr7LBOxWQFHBd3rArvJWO9Qp5a2q1WF7P3CAA5zt4TAhs5QjArEPGE/gAGcbOMSkwDlkgHao4y4CeahnGLinFdjucbDV89x+I8HohT52C0rrQiOtwHabl6oJOuAARz4vpm2ATr1rgNOp3VoASsBRW4EVdZ0uAtVHitLhzIoz5WE0OZzdjjGOfhilAnblAdiJ8dSJSZVHp8OX8t0z5AF4BOxkyMAhYHcVjy1b8ciA/T9JEKqQq/9Ds97dUs5bqeukw4kOvj28VSqcyODbUqcMnIoNOMABDnAq5NbAybQPN4UInB4cAHXA8BVOF1/h9AafASFuGcY7bgAAAABJRU5ErkJggg==" UUID="9aad43f8-e1fd-49a8-9f80-e533bc9e57d9">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s11" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAEVUlEQVR42u2di3KrMAxE+f+f1p07GdpAvKuVLWRInRkmKaUBDmu9/Oi2/b5sbT/b4WXr9ft6B7RoEECLRBTOuR0uOAuO3qz+KpgFZ8FZcBacR8B5clowBOf8JQiOAtD2/W9ba793rCWptxzO6/NrUwBF96lgso6R4DA57j/vYH5uxAG0nUCwfVEwSrMZhqNExhE474B6NuWGVbuSAkeVZwTOFUY0+jelcDyb413UAS58j4PxFJ+WeCr7VTm3jH0PnF7VpcFRn0BEOS1IUUAjzfISgzzirV7HeU30IXCiJ/GUo7lXDc6oUR+C03uSEQP41XC8CBlF2/vur4UTCgI/bjAPTsvYp8NpexKDNqcHTrRZ7Z/RzfMHcIG3sv2y/v8yOUJugfHgKKFCWW51VZyDVNNSClPRY3IrJWSH33/4OwXKWF1nQuJp0F4dt1NU+JF5Y1CKOm8Hh7lr6DVAWYI1q5ZhfpRykOu8Sz14GpxozJERyPVAmmaQmXr8IG+Tg0F2ztvAadZ9HVVlKwddy3zlNIrjVD2DG23CDeOO7GGZcqLq2aNo6/jMvpv1XHyGBBOaFbILUD3vrvr0vp0BbaLNAaFBORwe3frq+QBjHJCnHBQvpXYHh5uVtaXbvjDcvDbWtETltJLVM6S6ZmUaIPQUVfesekOumMpmRcBs0Bg6zUN8R+dhqimFAxVDwLCmFwsqsWJaD6neICNPsGkXmRcY6qopb1btURH44jNHiUVUYzO8FVPMp6fYUqJlC6rGZsFRFXNVk1KcwRQ4x9Be81TIDihVxY9zE9Wc4yQrN8goHXCljm8ej8zQwSPVlKYPsqyLvJQHZloQKEk90RC7gSZQXmniSSV9sftWFTOnZGGxYCzbS+Gk1mjFoBwOU41SfI8loCQCFioGpRFyVDGjiafnqXoGYF6aPrCyAS1+dSSgLFZSSimlxS5PMUphXC1+8QdjbhhRnls17UCgvBktm/rNE9ui2vSBQIkqZ7TgDu3SNtuVkwkgVDkDXTVS6XRugR1n6pHOuKyOPtSfVZx4Rvq05nUTT2lWUXVVDjDwRn9Mh8MgVQ9PuS0c5v6vgMOG2N4WjhcGwLHHZJicMkfjknHIV8LBHW5CMf7ne8yJkO2ZylHHHR8HHzAVKhG83ceVj45azx38jY8XxlDnKkebiMrtjjpqnRX/Uf/84Xjj0xhSxiFHJ2p4Y4/VSbC9cNQJMKnD+yNTfPRg8GiAR+EEQ4NcOPF5lxoc8nRDNufr4ERtoNJR+PVwaNMy3Q6SIDLPIPdNhO81yDbkrZRrewScSCJ5qzhndKZLzyRYua4kB40XRsijq5n0rmkxqpyy9CF7mZf+lU70CPlWuRUalJC18FA4fbjSIPfkVghOz9JVMADs9Faprnz4mIRFz6IRMiuGlcDpBdO7XJ68PF8FnG9YRFF0Fn96ec0FZ8FZcBacBecOYLyEeMFZcLoBLjgMzgJEwKx/4QT+hdM/ufxluQ4Xq50AAAAASUVORK5CYII=" UUID="c90327e4-cd6e-4dde-83c2-1723739225f4">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s12" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAE20lEQVR42u2djXLjIAyEef+X1k1v7toYa1crwEAaMuNp2iR2+NA/wi3l52Hn+D4uDzuPn8croEODADokVDieDh44AMynAoJwPEk6cA6cA+fAOXA2g/POacEUOMkLuM8v5/p+7XUg16NXeqfDYRdEM+fBsgpIDScamDLwYXCYSGbhQCBAcrxDnYRH4SjpQ4vksM/9fU7UqkU6H4MTzcp1sPgLIWlxz0PUip0/Y3iHw2GqwOAgiYHvcwAxtWrxTEPhKBLB4FglOZ7nQnCYarW67mFwlBlogQNtEQDUA6YnBOkyyJGrreGwGXz1WBYY46Vw1Iuw2c3CiaWnPzLvhpO5CJOcy0AzcF6kx8ikbA+H2RzP3si27JPhSBHsTb04HC+mGg4nE/Irhi9y4R7QLCAUcT/mrey/Jfl6kQaFd5sTxTfQBtXvI+q1NLfKxDnuwN3XeSR9D/40QEtzK+uUnDravQ/k/vMCMiFBj5csRsCpB+8Bqn96741yNvIFvydoCRymVh4cb+AIMvWWTILq1ytI0+FYkDN5KuNn3EE0DiJoD8zlvCQYfR6OYYNaQI0G2RgYLLqDdCSISM6QAnsrHCu66iBVo1DECfFsTnHipccN8g1OID2RxDBp8cBQj1l9Zjs4qnFmtsUDY4JBV+Os6XAi25MCAyqEbALY39fAueh6lFMFYGrbgSYiVTteCIdJD6oHQzBg8Jrk2H5w7kUuB4xntIkaUbhu3GN7wvFU65YaVHA8MFHUjW0KLl2s91aemtQLdAgOAaM4AaVYtxQOWmaBK5iotEGaGjJe6ZEWlG6bEwCqIRSxVhxJEEpTtnDlISBgRDtmFCa1UcC5XnJsfgcYz9veTHJGHkVw/9tIThHqPM/8zotnU+FEX2g2nMh7Ta8hs9hiplr5a20L4LCuTyUHeqKFlq3ILpMc3gE6yGW/q+TAdaeiFc/jYM9wgSsAtExyWopNuNfHcI4VNFQaybPWSI5qAwQJgmrkgamT1wrQ3QYtgoOWVLjaCGoEOy1QIb9+bRObEwIEbbTU9cM1cq2Ytl5yGpun3S8sgilCCRZ9bgs4UTBYWCWQ1I+1Wg1fAFzmykvD8gmSFiv60k4kPVvBYYNALbRZMOG1l8OJyhUwEcVgopZe1NezJRx/1cGgK0UdWwoYNX97VS1UUplXzxEahhgYpTajNCLgAtwiOErD0L9PwLiHtctlG55QhD4djtpq5gHN1IIz3RpR8jofDmgY4qUOS9R8cw2WkVNYA6dRJU20MYpNsd3hqC0hqNdPzd7d9zSolfA9x0qO2k1OAQm7khkcVXKEbQztcLo3ani5V+E79NA5s5tXxA0w/U3aLVt8/HV13aYxlfIS5MZNd2PhZPddFqERKSyxfgocZjzd38G+LKXLdHs4qBEyA4dLTW5L5GMGuUlqxL0KLXCYtxK3iM+HAyPgoJ2NrWlFFcslcU6POrnRLiyTxvFNFk709yFBYNt9JUC2ndjbaUVvzo5uovRohNzaPM2Xki1MQdTmbHV/+9LcSlWt6G5P2U2v0w1yT25FC1mBKkQ3LorgTHHlPdKF2lfQHQ1YfNUKB6YjS+s5Uddn0KGqGtUoBHkEzrvfRPExOL/hceAcOAfOgXPgbAZneBD4mwF1pw8fBu/AYXAOIALm/Asn8C+c/gAutVG/HN01PQAAAABJRU5ErkJggg==" UUID="3b7a097a-c1f4-4190-ae55-d189a19978c6">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
<tile width="71" height="96" name="s13" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAEX0lEQVR42u2d624qMQyE8/4v7fPjqFAWX2YcJ15KkBBqC2jzrT3xJUnHeD7kPB/Pl4ecx/PxG9Ch4QA6JCI4V7+zfnfgHDjvbnWF8I1QIDjfDMaF8+1gQjhntjqWU6s5n5wWLJ+tZi3q/+drn8g1lcOxgM1eSATo5+/IKzroKThoEIgMHLno53uM1wG+UhbbCIfyc2fQyJMFM+1WM3cgI4TDGXiVwJYJchZOdiAzA0ddl9UmCA76+5mp9OqqqK6h710CBxlQNuZB4VRpYCkcdEA74Gg3inG/UjjIoLy7ywJi3CoCuxQOOqgD58BZAwd9DwMH+ew2zbHEMRJk5j0IHOYz+6bynz8mk0okMrZuRDS1s+WVtjgnk1tlNQcJAnUrG/25FfNZ9BUR4o+HYwVy3oBZgBqIW8N5hYJF3N7vLN2xwNwSzlsh63KRniXFmvRa/zEt5m5w3gc+nLubi6v87/SvobWeo9d3h2pJUZlUhzVcqxhqtbAJzriUNpkBRVW/CHTkTq2W81tL0AFocGTo3YdZq2mznEdrBbAYz7XEEOsHKGOQqNX0WM5lAGGbJXAts4dlWkBsNdIKZ8QugP4cz1qYSy1tB1PtXGDW0TqYaEDITt8yhtpJ7YEjvo8jMU6UP+kpABbw6Tdos+awliMKoDiZ5KzmGlHvh2NoB2I5mvVEWbU4eiXmTCX31xxPd+KyBAgfaCvvdStCczK5VfYzFeuKSt3K0p/YcrDFSUyyitSvl0bIA8mnZI3l6NO03yZuSTztwpI2g9j1HUsrorxNd98bWI5/IZzleHAEAB5ZT0s9BylGIeWHjCBHNSC5i+UgXVPNrTKCjKcmuWVypZaDJJ8zliPAd5rBoWyGwwRf0czFBIFIgPny/jcr3TmVO7DgcimYPmR0p8mtggQv7B8xqy0sqIDlSINb2fWSOThsyQLWnB63Uu5gAGa22EVZTqfmeLGF1fmcKZOyZdcWzTG7CIxLJYrrMojNIl2a4/k46lJiVBSvd1zAVo8ZYnT1rQZhNYK4gGI5knAtzWJaLYcXYqA3DrhWFGdtTzwty5kR4kg/BlhcyyzX26M5E0tFZrJyePtSh+W4sxPYion2PUSJaOWKtFLL8S3GzsmQ5W5x97NuRVq95Yi//o5ZiO0vhpw7FKANTlRfxvInbgnun4IDL4gC3czWJBxOtJi8FI4nrp7uMMv6mQGGcMTfxlAOh3Er1o1mNqJkNsBsg+PdZWaV+qptTka8tg+O5/tZOCs20R04B86gv2uL5ljiiOx7QAU5s58UDQO2WU7UzumA0xLnIEFgZrbKAEIi5OCcjfVuNVO0zxT52+s5mSNaoq4Fc0ZOBujG3IpI+JgDg7TDhghAFRpYtsoCAYMdL5U7k4vNrW4BB94sAqzTySSV3nvQg9uW7fHsOO6Oue7o2L8l7eBPOkQRKK+cYzbL0oe/DujAOZbDxWwHzurZ6i+6UVkQeOCcx4GDwDmAHDDnXzgZ/8LpHzaQM+tabWPNAAAAAElFTkSuQmCC" UUID="00d3d8c0-a9d7-464e-a4fd-14c23f94184b">
<layer>69229e15-6b70-464b-806b-8897329438a2</layer>
</tile>
</tilemap>
editor.models.TileMapModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tilemap name="backside" rows="1" columns="16" width="71" height="96" x="48" y="110" UUID="5b188906-846d-4a90-8d89-e0e43a422abb">
<tile width="71" height="96" name="deck_1" fname="" image="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" UUID="c89dfa47-5c49-4c56-836b-19b7dd6e5651">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="deck_2" fname="" image="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" UUID="9e231907-746e-466c-b50d-1eb3db576f7f">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="deck_3" fname="" image="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" UUID="492b2a75-4ed9-431b-9c7f-c93330a33eb6">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="deck_4" fname="" image="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" UUID="e98cff41-88ec-47b0-bbd5-e684c97dc483">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="deck_5" fname="" image="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" UUID="c6c3c2d2-620d-4fdd-9fce-bc6190817c12">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="deck_6" fname="" image="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" UUID="b38f68aa-20ee-4be7-9840-136b838f7d8f">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="deck_7" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAC+klEQVR42u2di5LDIAhF+f9vdqb7arepkYcGFCKdYdrdTd14chUR0gK8H4+0f/t4fP+ipP3bG1DCIAB5OAnEtoYDj1LKn/28rmz1ubmA01JNwkEhJJwKQiqnCQezzeEURC3prQiXvs06x3qpHxbOc/54rWW0zRTQDDjIAk/LEs5WcKqhgZrguHvBqcKFj79Xr9Hj4sIBsvPHYPN0zOGZPO4Ex8J7GcEpAjhNgA04nHrswg0rONjVxiJwBA6pwsZFiK0cszknknK4TmFeqPWzyFvdCQ7luofWOXeHc1AJ3BkODMBpbXaNvC/DhzuHDyNbE1vBKaXzeaeoXEM18eEYW2A422+Tei8nWQbHcH94+R7zJTj2a5i185IIDibRyHAkw46FA4REo8IB4bAj4Zw7z8ZNjuaanvNuMxiE87HHS2xkrbBWVM+dty4cZpdv6TCi4ExRziQ4Q6HFajgwCGckdtJUzoQ5h0/YURmClmGQdJUj7dcCOJyHAXPlOIRTZy57AbUUQg7HqHA4QM1Olacd24Pya3A3OBSgWHA6/4nEU/ROsCbDShSYMnDo/ZFoi0Bpv1g4knoYG+VczkyQcHrqfIaUI4fzyh5YG6goB+Yq551aKcbPqZxUTionlbOlt9p+nUPdYhgfDt0vw9jKg7kMPD2lgf1F5ZHyVuZwgtgCOBEqLGRpbCM4GmUpM9tyAkcyH0nh6LWlBqc8xguBPMOR9msIjuRENoGDJeZEpSuO4ZiUoJwrKrabc+jotbAK8gpHJSqvZddIxJ9y3RFcOR5mdMNB76arK6g+APWFFRpz19XCyMvKIcvLTurRrBW2bWtMOVRal5x3PCtHcScQBcR6LK/KUUsHF3pj/aKnWjvnqCwCucbXzBNzbhQxvOk+jreafkuR5y0LB3B8x1YJJ+EknISTcBJOunLJlb58xTXbWg6nrzNc4KfXVsLxDEc3Zbv+UwoSzjw4VwuObNryAkezTGT5J6O4GlZg1NYtPmY8v28mHJj8CifkK5y+AA8evcu1KqggAAAAAElFTkSuQmCC" UUID="8c8ed8dd-8d4e-4c70-a267-ba27d105b572">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="deck_8" fname="" image="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" UUID="a4ea2b81-8403-4b5a-8413-e54da2e9b147">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="deck_9" fname="" image="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" UUID="0e995a0b-0ab6-42e7-9ed9-d4900cfbe304">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="deck_10" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAACXklEQVR42u3d0W7DIAwFUH/8HvawT0baqk1d0pSAAdv4giuhRNOUpifXhkTaSnS8vmP8j5fX4wcJcqTUNnjHPYBgYfRwDqBtYHrSo3rybVcqv/07Tn4LhHOc+CsQVQHzW2raTsW5nlAN5PVErr8vm5wWKGGc2pW7A0m3YLVEtfSc1iSZJqe1p1gAAc9W7yWXL8GYym+bN/fDyMM4w+lNDheoJ93wK+Qa0kjpG+MgXQg4HDJEN8U5PxKQOAZ1HpMLDIdTWklLAzfhEGO/fEIpSQElA+BmnNxBEXFUek5PrUv0icDBx3mWxlEiXnCkp/nlcCTfZ7myGp3mVXCoMu1v3ZBLVyxwCh88cAqLvMCJ5ASOGA5nBE7gBE7g9NwGBA57nNc7d/sb46Tq/r44jP3oOXv0HAqcUZyPz6/fETgZmNx+4AQOr6TWKa3AsW3I68xYLnAIFaftQ/bjEBoOGePQ+jjrAL3hjN8S9M1Ym+CMI22AM1Zq7nB0/6gUb3FoiNNXai5wbAdGgibhYNxqTMbx3agn4vhPDyQOBU4fmOQWrudYwUDOVhskRzY1CyVHHkYLzBjHDoawkgMIY4NjDwOSHGAYXRxcGOXkPFHm4DhPzrzURHLwkzMPByA52EBGs1WscxhNGQvIcBE4N0G9YMb3Vn6gQJ7n+J3uJz9Dnp8mh8+QOUj6UI6T4wPLeXJaepMOFlByuAtJWSSw5HDXTIpAWP9ZvwS0dXLubmrlymvB5OgAgSdHPj2LJKe2v2VySmmRb86LlJNcesCTUyor4dLC+YaiWlKEby2u31QU4wzzA3L0Sthlb1tdAAAAAElFTkSuQmCC" UUID="25000197-ea3d-4912-9e83-cddddaf0d7dd">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="deck_11" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAEQUlEQVR42u2di27rIBBE5/+/eaRWurluAM8uCzgPwJUQTepHGA7DLjgq8Pz5uctfyX5+QE5ZSDSV0HUTgbYRpkegW5xbnFucW5xbnFucW5z54pxVxGkRqOm6q4gTEaj5mn/ieKX3Ax/nlvWbRRr6/I8PbZRekZCcX9ajor+rVMUpRRoVpud6HxfnKoGiwnAmcdKGRUQKC1N4zkz0nGarkxkHxUEDFQgI/ZXiWI3wBELHcMGM5KgGeOJ4wkRFmlIcjyBUpmlMNCu9RBwlzBWhwFTiKIHQIAwnMeBLxbFE8ma7acWp5VaokOPmZxMOr1OEbEa0hjioGDQnFijLrYxG0airgswuUJWcQxBWhkqvQFORIwoLgWhRECVqusQTds5EBk0W0EmmCggxW1ZuHHAih5BTO9XsNppefLs42RCzgj6HvKl8ZijxVJ4TXaLAZAI1LVn0Rr+z0tO8nmORs8oCV0icw18EOdKQ2bq+PLk4VlbNHt9ZTRwyEOi1NhgrkoNk99BcP27cCV1CnH/CGL3+6YVyHJ8hqY/33yEOS3KupEE1BqMF+e8918xzK3GDckjJ49RFG2olijoGDikZMRCioLFOz8s8pEKO6gmLgKwcDRitveEl7g/nbzQ6iRDilL2jPIeVYUCjQd9WR4hu9pyqtwR7er4FdmNoqSEle+NFxNSGbss5TffJyCl6G/mQ8snBNdPy6EwXLuFHblIicudnzXPc+t2eg4sJjawEhsihpi8daryop2G9LokYJNr3HNpmHOoxBHu6Yzp/FZk95DBMDm0B3bE+4DVXCNIzW415DhvJ+bb4p0IOI56DK2aPzhlFEXgitpPSazxH5T9fGhl3kQPD2TNyLlqLwZu945rtYI+cxxB7eQM/nVPVI+QyEKyTw6/Pkzro8yNk6twquRhFYMfjfXEMa+s2lRjIPD+5D1snihg59QiZxbEMUMQGwig/uBCIojN6rj8U50ClEDkZx+vHV3h0DXn8uYEedapmSaR4XZLLQlD7Xv/FsRqmPIfZd5rSc3yBdB3vdU0lTg2mfB8GcbojmWbl5oGgfPykX4xZ6iA55VKpTc5adQc5vMnp85yV6oDnEDrHuslxyPHO2cdzkmdxeJMj1TuRlIu0q+eYj9uubsoBcvLpfC/vCZFD8RzyDqYcJOeZbNKMe3YmhzByLCxLkEMOhYpOxLygQB2eo7eIV/SgkOecBEFJzJoe1EgOxay1rgc1eg6ey5KoZe2cnqB4hGx8a095zioeZJKTrrGWZnx+cJtFcrqG97jkeMTs4D0nciC2LlScUxLF098X9Zznvk/+mtZa8qIBofAcnjyHcuPN9qgl45ySmNp0vXryWZADuUPo0YAFvCVIju85uy2yZw8SZOunxqxV7idvs/vgL0fssR1jeI4/S2ETQRo9Zy9BKp6z7uwzFiFv9RRFnBzsPoSM9aviPxXdJRPmF6EQLve7Oh6QAAAAAElFTkSuQmCC" UUID="25000198-ea3d-4912-9e83-cddddaf0d7dd">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="deck_12" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAADbElEQVR42u3djW7rIAwFYN7/mS31SpvaS4iNbTDh0HlStGlN1/XrwfyEraX8/3jl8TkuHy+iPN5HDfRXnuxQgr4W4f09IjIdP+d+E84Vg3my1dc9qDdM8zO/C4TDaZFkmOt9jsGxgkg4poRVtx+B4wXxJqeGqWvUMTifX76KfyjOBYjwa84tMS2SA0c/pwUCxmFf6TY55idOt7oj9VLU1B44HLEmGEGGcKrEEGrNkZpBadNibFpSb3WrRQwQFI46QKu/Vzcxw/26OOg1pzjrhxfH1o0TXs0pIz1PJA5qzSkTBdZSe6xzKbjkcPUgYtziwmESs73mcOOYFUC25BDWOKc3/rDOtGOSA1pztGWECCBfcsDGOb3m1Wtmszhak4IZ53Ddq6VIj9YnrZeCm1tZ5kAROJbEQM6tuF5rBoidMnRqDGxywgaFymTSmhjY9ZxRIDkZZKox8MkZAbo2mTKdGDphDdnVXbsSYrsdfoHdVJCDaswxa8ifQxsIukB8CcLGKTKQXnzHE0PHXLcSrkSONiHuku8x4xwJpimSAU2o4K7neJrU/RUfLcL35PDnwxdkqUZQWBEWE0hHJCeqF7Le/wSciYHb1PlIONdtaSWkFxq7PxAOtyRhXYBakiAUHHHNZioxo2BAONJy6IqhvwkUBceyYjff61jBgHCsa73lqQSh4EiXXvYkCAhH2zQ9eiVyOEE7cKS/G7Ds7LRcOZhP0CYcK8zMUbqz8bgaE4pTir6NxJMW/2K6Y0HrSRwJYrTODCP1VgLbGvYEjpyQ+23RyTFvQJKK/EocDWbkolxkTeody3D6NaV+cFpWmOfStQinX0uuMJZX9AQYE84ozO85b6D283NIy3FiYai7ozy+N1uA46kxXph2H/EKqIgpEYuzpinJMFqtgknObhhpaDBzhOAgwmiPoTXPUJyTYeRmuQDnVJhrKwjAKZ0HORUmsCBnYlSchBFwEqZTcxJG7a0SRkxOwojTh4RRu/KEUXD+OkxnEJgwwiAQCwYGBxEGAicK5ltSw8zKsWCAcBLGnJzdMGA4CaMmBwEGEAcHhv/ZG3GQYKIuyq3BSRgBBwBmd3PicRJGwAGCgcNBSgxecoBgjsB5vsbQC+1/+RTv7oqVMPA4O2Ait6stXCbdCwOLkzDK+80kzB0m38JJeAunf/qU3TPwoRQwAAAAAElFTkSuQmCC" UUID="25000199-ea3d-4912-9e83-cddddaf0d7dd">
<layer>040cb294-c4ad-47a5-abbb-6a7c8d1f3482</layer>
</tile>
<tile width="71" height="96" name="foundation" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAA4klEQVR42u3WuwqAMBBFwfvp+XMFkYgRJdvGKbbwUS3LcJKW7ZiYPn0nbXhhrn3cN/Xy0x+fMy7HTCwnk2e38neXU7kc5jCndjmMKYBsmMMcnaNzmMMcnaNzmMMco3N0DnOYo3N0DnOYo3N0jsthDnN0js5hDnN0js5xOcxhjs7ROcxhjs7ROcxhjsvROTqHOczROTqHOczROTrHMIc5OkfnMIc5OkfnMIc5rkTn6BzmMEfn6BzmMEfnMEjnMIc5OkfnMIc5OkfnuBzGMEfn6BzmMEfn6JwFL4cxH8/PUzLnTnYqjwQbNAxDhwAAAABJRU5ErkJggg==" UUID="25000200-ea3d-4912-9e83-cddddaf0d7dd">
<layer>69229e15-6b70-464b-806b-889732943899</layer>
</tile>
<tile width="71" height="96" name="talon_restart" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAABYklEQVR42u3dQQ7CMAxE0R6dmxuJRSlCqmiJPePkx/K64S2qJrHDtr1HkHt+jNge5J4HIDBOgIAABxxwwAEHHHDAAYcEBxxwwAEHnEP+EsvgjIjpcDKiPU5FtMNRRAscZVjjOIQdTtaPUSINwamaeDXQ3ziq90HFc8twlJ8JEhyHj7XMOdzGcVsTZcznFo7rYnH0vFJwlNsMUhxnmNFzvIQTk+CEAsdpF88Kx3GbswwnJsOJKhznTfJ0nJgUJ7JxOhyxgAOOGU5MjhNZOJ2OdsEBBxxwwAEHHHDAAScfZ/nlAwtPcMApx1l+m5QNdo5mONTjONjxrHzpQgJKUCheouxNXjAZRihlBZOU2lKkXVvePwKq8nmyxpArE1d1zgztt3IKmtGS32+0MVbjKJDa9pV3bJsuv5GgU8O9/C4LxysauAUFHHDAAQcccMABBxxwwAEHHHDA6YED0AkMf+H0na/xBK3MWmALjbJoAAAAAElFTkSuQmCC" UUID="25000200-ea3d-4912-9e83-cddddaf0d7de">
<layer>69229e15-6b70-464b-806b-889732943899</layer>
</tile>
<tile width="71" height="96" name="talon_end" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAB6UlEQVR42u2d2W6DMBBF+fT+eVpFarqpCbZnudccpHmLzMyBmFnhOL6OG/KQH8fteEMe8g0QMJ4AAgRwgAMc4AAHOMABDnAQ4AAHOMABDnA2hfPx67u4Gjyk/wicz4VdAQ3rfxbO74XdAE3pfwbOfwu7AJrW/xWcVwurA1rSP+LOUQW0rPfqnqMKKETflaeVKqAwPWf9HFVAofrNeMiqgML1mg0f1ACl6LMSW6kAStNjNfDsBpR6/oiovAtQ+nmjUhbVgErOF5nPqQJUdiGik13ZipfeoRmZwCwDyve2rDRptCEtm35mDjnKoDZ3ITvBvmpYqx9VUX2YNbDdA68qzYwaKhGaVNatzhosE9RWF/VswHRVPC3AdJaD5cF018qlwSg0EkjnpF3gXO7OYc9JeGJtDQc/p8BL3grOiNGXiq1mjL1EVL5i5Nb5nAjjtswERhq1VQ45w5gtqg+ZRljXrSqUt6x4Vl5Vq1p5x4Zp0WXR6YtI9+couPmSnV1KEbRUT6Bickqim1Q579vah+zQxd7Swe40/1A6++A4OVMyNeM8c5U6b+U+jHbWhjQ4zjOeqX8r5+ng1A3Zea489VHu/KaBVCeQ1zUgwAEOcIADHOAABzjAAQ4CHOAABzjAUYYDoCdg+ITTX7kf723NIVNzoSy+AAAAAElFTkSuQmCC" UUID="25000200-ea3d-4912-9e83-cddddaf0d7df">
<layer>69229e15-6b70-464b-806b-889732943899</layer>
</tile>
<tile width="71" height="96" name="backside_empty" fname="" UUID="8eb12cd0-f6ca-4f8b-9d38-50ec2945dc41">
<layer>69229e15-6b70-464b-806b-889732943899</layer>
</tile>
</tilemap>
editor.models.TileMapModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tilemap name="poker_animation" rows="1" columns="2" width="32" height="22" x="0" y="0" UUID="ab639c59-e44c-4c9b-a2f3-61b2c6b7086b">
<tile width="32" height="22" name="poker_1" fname="" image="iVBORw0KGgoAAAANSUhEUgAAACAAAAAWCAYAAAChWZ5EAAAA00lEQVR42sWVgQqAMAhE7/9/2igqnLlTm9FAlqP0eVOCCGRq2Hc5dpzPx67O7XuxL0Ms8ORPCCSTRFA3RFS59kGT5aGgi8rIr4nfJp0Vk1DAflyXm/nIyr/aeEQJJXHTHVcgEY3W3fkfKQEWHGb+0arE1YRR5Wb5irzwRSkAZ06HM20WpKyEAbDB3OQ6GFUkX/kAoAN5stvqKQibIgagA7kAyTXtkSxAqEYBJEo+BeiCQQeAf88/AVRAYH/hnQC0YR/JOcQSgO0RZp8BrPYB/ky+2wZmWjk4uHBnLAAAAABJRU5ErkJggg==" UUID="5a0c5e61-f5b6-4db7-a2a9-405e5fe3c039">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
<tile width="32" height="22" name="poker_2" fname="" image="iVBORw0KGgoAAAANSUhEUgAAACAAAAAWCAYAAAChWZ5EAAAA4ElEQVR42r2VCw7DIAxDff9LZ2rVVQ4hHyBbpYjRIfxijAoRiFu4RrlHPL/vkd6P6/K5qL0Qi1sIFEUyqBci65znCMXqUOCmKvYz8a6o10zBgRFi3e5ojhX7uRqdoO6C1L5HIGLfHTiB7GoZIS7+f9MJWHF95kac15Uc8aC+Iax0rpWmAAYkgxByAIUzVwCFx+4LI/5cQ73YPfPNx2RkBjBeM2P3AQDvHwLohTJP/QFMGcC40QDgibsALkxj52UAN9kN4ssAKyAYP+GdANPAuuKHGVjJSFQ/A/hrCLvFr/oAoqlQIUMaf4AAAAAASUVORK5CYII=" UUID="1a6307f6-0806-496f-9b4f-afbd7cbd7bba">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
</tilemap>
editor.models.TileMapModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tilemap name="castle_animations" rows="1" columns="1" width="26" height="12" x="0" y="0" UUID="96f80568-84f2-4dd1-852b-11b108fddb82">
<tile width="26" height="12" name="castle_0" fname="" image="iVBORw0KGgoAAAANSUhEUgAAABoAAAAMCAYAAAB8xa1IAAAAR0lEQVR42mNgYPj/nxiMDojVh8AM5Ggi2yIGnJI0sAgXJt4g/OJYBMjzDSHHUjkecDuWvolhZFlEraRPcWqiatBRmvT//wcAhK1Ry7OMezoAAAAASUVORK5CYII=" UUID="b1ea9525-0e4e-4fc8-8a63-55b2bcf77a95">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
</tilemap>
editor.models.TileMapModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tilemap name="robot_animations" rows="1" columns="2" width="24" height="7" x="0" y="0" UUID="f948f215-40bf-4dca-bb31-788f73156cb8">
<tile width="24" height="7" name="robot_0" fname="" image="iVBORw0KGgoAAAANSUhEUgAAABgAAAAHCAYAAAAS9422AAAAQ0lEQVR42r2QQQoAIAgE5/+f3qAQPLiQCRlzsMMsKwIFGDSALHbjgrhBhbwKe22ClZkmroX7GwWcXRuyPN63gA7dEy1wvWen+eMQ9AAAAABJRU5ErkJggg==" UUID="1e9ff1c3-ee06-4142-90f5-ec081d91060b">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
<tile width="24" height="7" name="robot_1" fname="" image="iVBORw0KGgoAAAANSUhEUgAAABgAAAAHCAYAAAAS9422AAAASklEQVR42q2QSQ4AIAjE+v9Pj/GAISriBumFQycMAhkE6AG8OJooiB2UyH3I1QdengWtvohu9NKsqkFk626qClq1zEU/A044ragAZQ9qpCnFAgkAAAAASUVORK5CYII=" UUID="2fc57747-aa27-41d5-b348-ed2be68fa384">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
</tilemap>
editor.models.TileMapModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tilemap name="beach_animations" rows="1" columns="2" width="14" height="12" x="0" y="0" UUID="5365e20a-c399-4cc4-a780-de751ca7fdaa">
<tile width="14" height="12" name="sun_0" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAA4AAAAMCAYAAABSgIzaAAAAU0lEQVR42p2RCw4AEAxD3/0vPSF+Y8ZImphqZwqSFyHUHfIDJYQB76wLZ/IFVRPvWOrt7Rjz4MxokZ4BN+cTx5qj9yEqx1YGwxfl9SxahScD604CkUGqZO4bEkQAAAAASUVORK5CYII=" UUID="a0531693-d6c6-4c30-98da-00dfe117c8b2">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
<tile width="14" height="12" name="sun_1" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAA4AAAAMCAYAAABSgIzaAAAAV0lEQVR42p2RiQoAIAhD3///dFF0rdKOYJCOzZYQ0uEJ5Ub4gQihw+s14UjeoGjeJ+Z6eTubPDgZd6RnwMnZ4pj36H2I7LGWMoEJ6/K1fRKKhZWqiiw+AsLXoG5EnP5iAAAAAElFTkSuQmCC" UUID="9200227b-5948-4d98-b64a-17846b71b717">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
</tilemap>
editor.models.TileMapModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tilemap name="backside_animations" rows="1" columns="7" width="71" height="96" x="54" y="250" UUID="6ec291a6-ead4-4551-9186-c6dda3616fbc">
<tile width="71" height="96" name="deck_7_1" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAADB0lEQVR42u2dbXLEIAiGvUPv1fufpL+cST/SzSZGEBUUIjvD7EezNj55FVHchPB+bG6HXR4/H0S3w96AHAYCSMNJALY0nLDFGHf7fZ3Y7HNTASenGocDQnA4CQRXThYOZIvDiYBa3FshLn2ZcY70UN8snP/+4zWW4TZRQCPgAAM8LnM4S8FJmgZohOOeBScJFy5/T16Dx9mFE9DKn4PN2zGnZ/S4GxwJ7yUEJxLgZAFm4JTUIxduSMGBrjYUgQNwUBVmLoJt5Yj1OZaUU6oU5IVy70ne6klwMNfdNM55OpyTSsKT4YQGOLnJrpbvefjw5PChZWpiKTgxVj6vFJVzqMY+HGEzDGf5aVLt6STT4AjOD0+fY+6CIz+GmdsvkeBAErUMh9LsinACIlGrcAKx2aFw7pUvxk2K+pqa884zaIRzmeNFJrJmWC6qL503L5zCLN/UZoTBGaKcQXCaQovZcEIFnC18HAZV8HxMrdXAGdDnlBfsUjgHBOABQeJVDrVeE+CkMHKAZJWjEA4IAlAQpB7os2Xh7O+//iycm+j2uZv1ZvVcOJX/hOIpSjakWZEC0wIcfH7E2iCQWq8iHEo+jIxyulcmUDg1eT5NyqHDea0eSFtgUU4Yq5z30koUfnbluHJcOa6cJb3V8uMcbIuhfTh4vQRjKw2mMvDUtAysLyq3tG4lDseITYBjIcOCtowtBIcjLWVkWUrgUPojKhy+stjgxK09EUgzHGq9muBQTmQROLnF+PL2Hf1wRFJQ7hkVy/U5ePQaiwrSCoclKk9ll1mIT2IZG64cDjOq4YC76dIMqgugurCCo+/qTYzsVg6aXnZTD2eusGxZbcrJbuaIWRX1BKRjlcM4EwgCKnosrcphWw6O+MR6p6ea2+ewDAJLhc/pJ8ZsFBHcdG/HWw3fUqR5ykIBHN2xlcNxOA7H4Tgch+OunHKlu684Z1nT4dRVphT48ZXlcDTD4V2ynf8rBQ5nHJzehCOZsrTA4UwTmf7LKKqaVRAq6xE/M+73mzEHxm/hBNzC6Rt9t8K1ssa9egAAAABJRU5ErkJggg==" UUID="fdf06b20-0845-4acd-9661-9d01d8efee4a">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
<tile width="71" height="96" name="deck_7_2" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAADC0lEQVR42u2dW3LDIAxF2UP31f2vpF/MuI80iY3RAyyBZOQZTdLUpeb4ghAIJ6X3sYW97HD8fJDDXvYGFDAQQBYuArCl4aQt5/yw3/eFzb42E3Bqqgk4IISAU0AI5VThQLY4nAyoJbwV4tKXGedoD/XdwvnvP55jGWlTBTQCDjDAk7KAsxScommAxjjvXnCKcOHw++I9eJ5fOAmt/D7YPJ2ze0XPO8HR8F5KcDIDThVgBQ6lHr1wQwsOdLehCByAg6qwchN8K0etz/GkHKpSkBeq/czyVneCg7nurnHO3eHsVJLuDCd1wKlNdvX8XYQPdw4feqYmloKTc+PrSlG5hGr8w1E2x3CWnya1nk4yDY7i/PD0OeZLcPTHMHP7JRYcSKKe4XCaHQknIRL1Cicxmx0K51x5Mm4y1Ne0XHedQSecwxwvMpE1w2pRPXXdsnCIWb6pzQiDM0Q5g+B0hRaz4aQGOFv6eBlUwf05rdYCZ0CfQy/YlXBeEIADgiSrHG69JsChjl4FuYZDKYYCVFPV/rNbwql9XoWwfT5sr8L09WfumxUIgdG0bMNp/CccT0HZkGbFCkwJOPj8iLdBILdeJBxOPoyOci6vTKBwWvJ8upTDh/NcPdC2JKKcNFY576WVrPwaygnlhHJCOUt6q+XHOdgWQ/9w8HopxlYWzGTgaWkZ2F5U7mndSh2OE5sAx0OGBW8ZWwmORFrKyLKMwOH0R1w4cmWJwclbfyKQZTjcenXB4VzIInBqi/H09h37cFRSUM4ZFcv1OXj0mkkFWYUjEpWXsqssxBexjA9XDocZzXDA3XRlBtUBUFtYIdF3XU2MvKwcNL3spB7JXGHdsvqUU93MkasquhKQjlWO4EwgCIj0WFaVI7YcnPGJ9Yueam6fIzIIpAqf00+M2SiiuOnej7cavqXI8pSFATi2Y6uAE3ACTsAJOAEnXDnnTl++45JlTYfTVhkq8JMrK+BYhiO7ZDv/KQUBZxycqwlHOmVZgSOZJjL9ySimmlVSKusWjxmP75txBya+wgn4CqdvvInFsvgHZO4AAAAASUVORK5CYII=" UUID="bc2a584b-5149-40cb-83d0-a5e67a69f9a6">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
<tile width="71" height="96" name="deck_10_1" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAACbklEQVR42u3dy07EMAwFUH8FS76WBQu+l1UkHoNKH5M0TuI4volHihoQKu2Z6zwqwRDtry9v/+30+vlGgGwhlDXeeXcgWJh+ODvQMjA16el68WXvVPz4d574EQhnv/AzEGUB40cqOg7FuV5QDuR8Idefl01OCZQwTu6dS4GEJFguUSVjTmmSVJNTOqZoAAHPVs8lFy9Bn8qTgzf3ZuRhjOHUJocLVJNu+BVyDqml9JVxkN4IMJzXl8/HOX6PW9u+Bsc5PhKQOAdVnZMPDIhzvaGtLw9chEOM/v0FhSAB1P4m8YCLcWL7HzScbmNOTa23jxOOMwPOVhp7idjAkZ/mJ8KR/z1TlZXENN8FhzLT/tIDcuroOJkbd5ybRZ7jeHIcRwyH0xzHcRzHcWq2AY7Dbsf1Tqq/ME7I9tfFYfR9zFljzCHHacV5e/94NMeJwMT6juM4vJKap7QcR3dAnmfGMoFDqDhlN1mPQ2g4pIxD8+PMA/SE074lqJuxFsFpR1oAp63UzOH0/aNSvMWhIk5dqZnA0W0YCRqEg7HVGIxje6AeiGM/PZA45Dh1YJJHuDFHCwZytlogObKpmSg58jC9wJRx9GAIKzmAMDo4+jAgyQGG6YuDC9M5ORvKGBzjyRmXGk8OfnLG4QAkBxtIabbydQ5jUMYCUlwEjk1QLZjy3soOFMjzHLvT/eBnyOPTZPAZMgepP5Th5NjAMp6ckrGpDxZQcrgLSVkksORw10wdgbD+s/4d0NLJSW1q5cprwuT0AQJPjnx6JklOrr9kcu7SIj84T1JOcukBT85dWQmXFs4nFOWSIry1uH5SkbcjzDcefE+y2QfxkwAAAABJRU5ErkJggg==" UUID="90e8f2f4-80e9-46f3-b56c-f60c072aed88">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
<tile width="71" height="96" name="deck_11_1" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAEb0lEQVR42u2djXLbIBCE9xX7/q+yE7dJo5ifveNAsmPAmWGIZSSF5WO5Q+oUuP/c3uWnZD83kPOV28ft4+NPV/k8p3ndRKA5hflXeoX5EShy/W+BlhAnnRbesa3E6fWTbcRpUWIe221aHR0PHdtJHCWAJ9KWhhwRZh/PKeIcz4S74pxVxOkJBI/2W4kTESht2yeOV04ss7J+kDiWSKpNJzlGGRUJyfllfVb0J8ZQvjilSGeFGbner4tzlUBRYTiTOGnHIiKFhSk8ZyZ6qtWqMuOgOOigAgGhX1IcqxOeQBiYLpiRHNUBTxxPmKhIU4rjEYTGMo2JVqWHiKOEuSIUmEocJRA6hOEkBnypOJZI3mo3rTit3AoNctz8bMLpVUXIZkRriIOGQXNigbLcyugUjbopyOwCNck5BGFjqowKNBU5orAQiBYFUaKmSzxh50xk0GQBnWSqgBCzZeVGg4ocQi7tVKvb2fTi1cXJppgV9DnkTeUzpxJP5TnRLQpMJlDXlsVo9DsrPd37ORY5q2xwhcQ5/EWQIw2ZvfvLk4tjZdUc8Z3VxCEDgV5vh7EiOT9i0dk/7nwSuoQ4X8IYo/7bG+U4/oakPo4/QxyW5FxJg+oMzhbkv49cM8+txA3KKSXbqYt21EoU1QYOKRkxEKKgs07PyzykQY4aCYuArBwdOFt700vcH853NAaJEOKUo6M8h41pQKNDr1ZHiO72nKa3BEd6vg12Y2qpKSVH40HEtKZuzzld98nIKUYb+ZTyycE1y/LZlS5cwq/cpETkzs+W57j1sz0HFxMa2QkMkUNNXzrVeNFIw/pcEnGSaN9zaJtxaMQQHOmB5fxRZI6QwzA5tAV05/oJr7lCkJHV6pznsJOcV4t/GuQw4jm4YvUYXFEUgRWxg5Re4zkq/3nRyHiIHBjOnpFz0V4Mnuwd1zwO9sj5P8Ue3sHfzqnaEXIZCLbJ4cvnSQP0+REydW6VXIwisONxXLRha9+mEQOZ5yf3Ye9CESOnHSGzaMsARewgjPIPFwJRDMbI9U/FOVApRE7G8flLPKOGbF930KNO1SyJFJ9LclkIat/rWxyrY8pz7t+X5/gC6To+6ppKVB2mPA6DOD2QTLNysyEoXz8ZF2OWOkhOuVVqk7NWPUAO3+SMec5KdcBzCJ1jvclxyPHO2cdzkndx+CZHqleRlIu0q+eYr9uubsoBcvLlfC/vCZFD8R7yDqYcJOeebNKMe3YmhzByLCxLkEMOhYpOxLygQAOeox8Rr+hBIc+pBEFJzJoe1EkOxaq1rgd1eg7u25JoZe2cnqB4hGz8qz3lOat4kElOusdamnH94jaL5HQN73HJ8YjZwXsqciAeXag4pySK1feLes79uU/+mdZe8qIBofAcVp5D+eDN9qgl45ySmNZyvXryWZAD+YTQowELeEuQHN9zdttkz14kyPZPjVWrfJ68zdMHfztij8cxhuf4qxQ2EaTTc/YSpOE5664+5yLkrd6iiJOD3aeQsX9V/E9F75IJ8xfUiQfRQ/fDAAAAAABJRU5ErkJggg==" UUID="4e0b645a-38fa-425e-bedb-c61c1061f714">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
<tile width="71" height="96" name="deck_11_2" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAEd0lEQVR42u2dC3IbIRBE+w45We5/lS4rieO1+PQMA7uSBchVFNZqP+bRNDOwqQD3n9u7fJfs5wZyvnL7uH18/O4q/65p3jcBNCeYv6UXzDegyP2/AC0BJx0W3rGt4PT6yTZwWioxj+02rI6Gh47tBEcB8CBtacgRMPt4ThHneCbcFeesAqcEdMOvrFRgdoOTArLgpOf2wfHKiWlW1g+Ck5YDjPV9p3KMMgoJyfVlfRb6E2MoH04J6SyYkfv9OJyrAEXBcCY4acMikMJgCs+ZST3VbFWZcRAOOlSBAOiXhGM1wgOEgeGCGZWjGuDB8cBEIU0Jx1MQGtM0JpqVHgJHgbkiFJgKjgKEDjCcxIAvhWNB8ma7aeG0cis0lOPmZxMOrypCNiNaAw4aBs2JAWW5ldEoGnUTyOyAmso5gLAxVEYBTaUcUVgAoqWCqKKmSzxh50xk0GQBnWSqgBCzZeXGCZVyCDm1U81uZ9OLV4eTDTEr6HOUN5XPnEo8ledElygwGaCuJYvR6HdW9XSv51jKWWWBKwTn8BehHGnI7F1fnhyOlVVzxHdWg0MGAr3eBmNF5XzDorN+3LkTugScTzBGr//0QjmOvyGpj+PPgMNSOVeqQTUGZwvy30fumedW4gHlkJLnqZt21AqKOgeOUjLFQEBBZ51el3lIQzmqJywFZOVowNnaG17i+XC+o9FJhIBT9o7yHDaGAY0GvVodUXS35zS9JdjT8y2wG0NLDSnZGw9STGvo9lzT9ZxMOUVvIx9SvnJwzbR8dqYLl/ArN6kicudny3Pc+tmeg4sVGlkJDCmHWn3pUONFPQ3rc6mIk4r2PYe2GYd6DMGeHpjOH6XMEeUwrBzaAN2xfsJrrgAyMlud8xx2KufV4p+GchjxHFwxewzOKEqBlWIHVXqN56j850Uj4yHlwHD2TDkXrcXgyd5xzXawp5z/Q+zhDfzpnKodIZeBYFs5fPk8aUB9foRMnVslN6MI7HgcF+ewtW7TiIHM65PnsHeiiCmnHSGzOJcBFbFDYZR/uABE0Rkj9z8V50ClELkyjs+f8Iwa8vy6gZ7qVM1SkeJzqVwWQO1nfcGxGqY85/59eY0PSNfxXteqRNVgyuMwFKc7kmlWbp4IytdPxmHMUgeVUy6V2spZqx5QDt/KGfOcleqA5xA6x3orx1GOd80+npO8i8O3ciS9Skk5pF09x3zddnVTDignn8738p6QcijeQ97BlIPKuSebNOOenZVDGDkWllWQoxwKik7EvCCgAc/RW8QrelDIcyogKBWzpgd1Kodi1lrXgzo9B/dlSbSydk6voHiEbPyrPeU5q3iQqZx0jbU04/rFbRbJ6Rre4yrHU8wO3lMpB2LrQsU5paJYfb+o59z3ffLPtNaSFw0Iheew8hzKjTfbo5aMc0rFtKbr1ZPPQjmQO4SeGrCAtwSV43vObovs2YsE2fqpMWuV+8nb7D74yxF7bMcYnuPPUtgESKfn7AWk4Tnrzj7nIuSt3qKIKwe7DyFj/ar4n4reJQPzB+Rc+tRfOUIXAAAAAElFTkSuQmCC" UUID="722fa391-4a49-4ddf-b954-4a189cd2f562">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
<tile width="71" height="96" name="deck_12_1" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAD0klEQVR42u3dAXLiMAwFUN+hJ9v7X8XT7MI2wVEkS7IV/E2dmQwFAiWv37IdQknptWxrPdbTsuW81n0tgX7LzjYl6GMR9ttyzqb1ue0n4ZwxmJ0tfq5B7TDkOT8LhMOhSDLM+THT4FhBJBxTwor7p8DxgniTU8KUNWoanOPFF/EPxTkBZfyac0kMRXLg6NtQIGAc9i9Nk2Pe8XypO1IvlUntgcMRa4IRpAmnSExGrTlSM0g0LcamJfVWl1rEAEHhqAO08rayiRkeV8VBrznJWT+8OLZuPOPVnNTS80TioNac1FFgLbXHOpeCSw5XDyLGLS4cJjHDaw43jrkDyJacjDXOqY0/rDPtmOSA1hztMEIEkC85YOOcWvOqNbNeHK1JwYxzuO7VUqRb65PWS8HNrSxzoAgcS2Ig51Zcr9UDxE4ZKjUGNjlhg0JlMmlNDOzxnFYgORnZVGPgk9MCdG4yqTsxeYZjyK7u2pUQ2/3wB9hNBTmoxkxzDPlYtYGgC8SXIGycJAPpxbc9MVA15/v7j7hu6Wt7Lv8uH6/z+ePjsridbqddP57j5xJ6nFOD4YAeqwVAA9uBoOdWWmLK6xyMJzE0OY/r1yYFjlP7S7eCSNCXmpMnSg7dsZYmVLvOwcDiWJLQUoSl+3Oqnxc4cID3KrB7LxRRUzyA2kmTw86esHTP+/Z3JQgGRzpmI73wA6ZAikrQ3owhcKTDoWpiyKI9znodBsdyxI6OQ063lStFciaIdgBDcbSZNd1RFqbc0UqSPIkZjiO99UJxyp3kmhJNTQ2p1tvB4GgnTVOccidZHOMi1aQhONLnBixndnI4aoocSBrMrThWGK3mWJLQArU3s7fjpKSfRmJ5/8m7sx6kITgShLXOtOJ4kMre7204ckKu90Unx1S8CYwGFIajwbS8Kde7WD9YdhtOvaa8YLynsUXg9NadLpx6LTnDaG/kW8Y574TpwmmF+b/NDkQv2z8i1PaRor4PwTaMclthcvWM8kiUoMFtX43xwtDziO+AipgSsTj3NCUZRqtVMMkZDSMNDXrWEBxEGO13aM0zFGdmGLlZ3oAzK8y5FQTgpMovmRUmsCCvxKg4C0bAWTCVmrNg1N5qwYjJWTDi9GHBqF35glFwfjtMZRC4YIRBIBYMDA4iDAROFMynpIaZlWPBAOEsGHNyRsOA4SwYNTkIMIA4ODD8cw/EQYKJelPuHpwFI+AAwIxuTjzOghFwgGDgcJASg5ccIJgpcN5fY/KG9r98kvfsijth4HFGwESernbjYdKxMLA4C0b5vpkFc4VZX+EkfIXTX0VYUj6lwmaOAAAAAElFTkSuQmCC" UUID="e2ca4158-d3ec-468b-9afd-316d2e160587">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
<tile width="71" height="96" name="deck_12_2" fname="" image="iVBORw0KGgoAAAANSUhEUgAAAEcAAABgCAYAAABPGW+RAAAD00lEQVR42u3dDXLjIAwFYO7Qk+39r8LUu8nWDhYSEiDCI8UzTJqfpvVX8QDHaUJ4bcduV7ttR4y7nS0F+i0721RBH4tw3hZjNLXnYz8J547B7GzydQnqhCHP+VkgHA5FkmHu37MMjhVEwjFVWHL/Eji1ILWVk8KkGbUMzvXLJ+XvinMDiviZk1UMRarA0R9DgYBx2L80rRzzjscsd6RRKpLsgcMRM8EI0oSTVExEzRypGwRaLcauJY1WWRYxQFA46gQtvS3tYobvK+KgZ06ozI9aHNswHvEyJ7SMPJ44qJkTOgLWkj3WtRRc5XB54DFvqcJhKmZ65nDzmBFAtsqJWPOc0vzDutL2qRzQzNEOI3gA1VUO2Dyn1L1K3awXR+tSMPMcbni1hHRrPmmjFNzayrIG8sCxVAzk2oobtXqA2CVDIWNgK8dtUqgsJq0VA3s8pxVIroxoyhj4ymkBuneZ0F0xcYVjyFXDdVWF2O6HP8BuCmSnjFnmGPLVtIlgFUhdBWHjBBlID9/2ioHKnO/vP2I7wtfx3P5dPn7P55ePy+R2+jjt+vUcP5fQ85wSDAf0aBYADewEgl5baRWTXudgaiqGVs7jet6lwHFKf+lWEAk6y5y4UOVQoJYuVLrOwcDilP7SafOqoBjK5wVOnOC9AvbcYWl0ubrVz3a7raOCtJMmp509YRmeM4S0pfc3VhAMjnTMppQxGUz6OEMlSWBnN4bAkQ6HmirmpvDF4mRIChAMjuWInZYxNxzDRp+XVgwEjraypjsiZkzjRjMJBkd66YXi0KE660IdOOnzw+BoJ01TnHQnWJxOqCk40vsGLGd2cjhZFTngSDBDcawwWuZYMkMM6A6YYTgh6KeRWF5/agpXJ5ghOBKENWdacWqQ0tHvbThyheT3eVdOMbwFmLdljgbT8qKcxxBtacNwypnygqk9jc0DZ2ogl7PkDqO9kG+Z57wTpgunFeb/Y04getn+FqG2txT1vQm2YZbbChOLZ5R7ojhNbvsyphaGnkc8AspjScTijOlKMoyWVTCVMxtGmhr0NBccRBjtZ2jd0xVnZRi5Ww7AWRXm3gsccELhh6wK4xjIu2JUnA0j4GyYQuZsGHW02jBi5WwYcfmwYdShfMMoOL8dpjAJ3DDCJBALBgYHEQYCxwvmU6qGWZVjwQDhbBhz5cyGAcPZMGrlIMAA4uDA8M89EQcJxutFuTE4G0bAAYCZ3Z14nA0j4ADBwOEgVQxe5QDBLIHz/oyJB9r/8gm1Z1eMhIHHmQHjebrawMOkc2FgcTaM8nkzGyaH2R/hJHyE01/m3WJpyJD2mQAAAABJRU5ErkJggg==" UUID="b4d443c9-6606-44df-a294-a3a78e57bad2">
<layer>08b388db-1b65-4952-8dff-8664adacf4c8</layer>
</tile>
</tilemap>
editor.models.ProjectModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<project name="Solitaire" UUID="224afa8d-1775-4831-8127-624b5397024f"/>
editor.models.TileLayerModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<layer name="hearts" UUID="06be52cc-1d25-46cf-815f-2f47f0d4ae26"/>
editor.models.TileLayerModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<layer name="clubs" UUID="41e4ef0d-8383-4cc2-a31f-01041591ab40"/>
editor.models.TileLayerModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<layer name="diamonds" UUID="b0785b21-2e95-4eb0-8e4d-73f9748013fe"/>
editor.models.TileLayerModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<layer name="spades" UUID="69229e15-6b70-464b-806b-8897329438a2"/>
editor.models.TileLayerModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<layer name="misc" UUID="69229e15-6b70-464b-806b-889732943899"/>
editor.models.TileLayerModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<layer name="backsides" UUID="040cb294-c4ad-47a5-abbb-6a7c8d1f3482"/>
editor.models.TileLayerModel
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<layer name="animations" UUID="08b388db-1b65-4952-8dff-8664adacf4c8"/>
================================================
FILE: assets/initial requirements.txt
================================================
Solitaire Requirements
- Solitaire window icon
- Game Menu (Deal F2 | Undo, Deck..., Options... | Exit)
- Help Menu (About Solitaire)
- Clicking on deal should start a new Game
- Click on undo should undo the last move played
- Display the number of cards remaining on the stack
- Deck should display a list of predefined deck images
- Clicking on Options should display the options window
- Options window - draw one
- Options window - draw three
- Scoring - Standard
- Scoring - Vegas
- Scoring - None
- There should be a way to display information at the botton of the Game
- Timed game toggle display
- Status bar on/off
- Outline dragging on/off
- Support for dragging a card and detecting edge hit
- Support for dragging a card and the card image follows the mouse
- When double clicking on a card, if there is a move it should perform it
- When there are no more moves left to make, the game should be over
- Game over
- Game win animation
- The game should support music and sound effects
================================================
FILE: data/generated/tilemap.xml
================================================
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="e984a82d-092a-454f-984e-e38606a7f7e5" name="c1" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="0" y1="0" x2="71" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0adc2583-1a94-48fd-9b6e-b1a141bb76e2" name="c2" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="71" y1="0" x2="142" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="2373e07c-6a5d-404a-bf07-14b2211653f1" name="c3" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="142" y1="0" x2="213" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="92298418-a59d-44df-89c9-7e0c028131f2" name="c4" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="213" y1="0" x2="284" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="76153ba2-26c0-4c1e-8ba4-2b558f948138" name="c5" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="284" y1="0" x2="355" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="dfe50140-8e9c-41cb-b21d-c0af51b78436" name="c6" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="355" y1="0" x2="426" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="fb2435a2-6492-439b-8d34-03e8229a3b8e" name="c7" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="426" y1="0" x2="497" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0a0ecf52-7060-42f7-9d9d-72ffe26c8907" name="c8" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="497" y1="0" x2="568" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="7a84e376-6c0d-4ba6-a27f-adcf2a41476f" name="c9" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="568" y1="0" x2="639" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="f690e1b0-3c1b-4fd4-8754-09d5823bc76a" name="c10" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="639" y1="0" x2="710" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="ab0658dd-6958-4503-85ae-380dd54ec310" name="c11" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="710" y1="0" x2="781" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="58d2569a-ebd9-4049-b662-7c2dd68968c9" name="c12" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="781" y1="0" x2="852" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="9a666fae-7f67-4afa-8c4d-142d62c782d2" name="c13" layers="41e4ef0d-8383-4cc2-a31f-01041591ab40" friendly="" x1="852" y1="0" x2="923" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="d59b8ca9-5fa0-4f46-b7ac-93869a30117a" name="d1" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="0" y1="96" x2="71" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="9ec05b58-89fb-4689-b800-175cc034fab1" name="d2" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="71" y1="96" x2="142" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="d972f5c7-687b-4f65-aa17-f297ba99bc0e" name="d3" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="142" y1="96" x2="213" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="4f89e5ae-3554-4465-a6df-c409b5030fa2" name="d4" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="213" y1="96" x2="284" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0b1e1a44-f5c7-4fc7-b1b3-1ea0fbfae73a" name="d5" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="284" y1="96" x2="355" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a8f33327-dfbd-4989-805d-88847490ff20" name="d6" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="355" y1="96" x2="426" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="3ab272fa-f8cd-4c54-8c75-16ce3b5361c9" name="d7" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="426" y1="96" x2="497" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="61c0327c-0e96-4893-8dc1-4692e046bb6b" name="d8" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="497" y1="96" x2="568" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="44672b8c-c9c1-434d-803a-fca1b30ec92b" name="d9" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="568" y1="96" x2="639" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="29e7d023-9cf2-4776-beef-5f390a6fbc59" name="d10" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="639" y1="96" x2="710" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a582dd25-9a24-46c2-ba98-1927e3e55ac5" name="d11" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="710" y1="96" x2="781" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="ad1b28a4-61a9-4451-9759-9565cd7b0626" name="d12" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="781" y1="96" x2="852" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="6e1a2bea-c6fd-4ec0-a800-8d65fd038674" name="d13" layers="b0785b21-2e95-4eb0-8e4d-73f9748013fe" friendly="" x1="852" y1="96" x2="923" y2="192"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="2cc80226-f795-45e4-8c85-4f9acf7a1fb1" name="h1" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="0" y1="192" x2="71" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="879ae6f3-950e-44a0-82c5-4752e957c1b7" name="h2" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="71" y1="192" x2="142" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="d110f5d3-a456-4a14-a48a-33d03703e205" name="h3" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="142" y1="192" x2="213" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="956d7658-3fc6-4422-84ae-2c0027e416e2" name="h4" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="213" y1="192" x2="284" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="1be5f51f-7a2a-4d0b-8100-ad3343f796f5" name="h5" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="284" y1="192" x2="355" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="1fbbdf52-331a-47f7-8d1e-e84398cefda8" name="h6" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="355" y1="192" x2="426" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="53fe97cb-710f-4630-9c1c-8cdc03c77212" name="h7" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="426" y1="192" x2="497" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="298a41cf-2be4-422a-9b59-f1aadc41f48e" name="h8" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="497" y1="192" x2="568" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="db19c7f8-44d0-4e03-a848-747a3a31da9c" name="h9" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="568" y1="192" x2="639" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="934f6d8b-bbbe-43b1-a28d-913c5c80fef9" name="h10" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="639" y1="192" x2="710" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="7c588781-b5d9-493c-86dc-6b383cc33098" name="h11" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="710" y1="192" x2="781" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0d75364f-a431-41dc-9a23-75b0e200ffce" name="h12" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="781" y1="192" x2="852" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a3397bc9-187c-461a-b336-7059f6b8eadc" name="h13" layers="06be52cc-1d25-46cf-815f-2f47f0d4ae26" friendly="" x1="852" y1="192" x2="923" y2="288"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a4e9de58-b1c8-4a46-8b60-71c4c6a463c9" name="s1" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="0" y1="288" x2="71" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0db5fd69-a18f-4024-8a6b-b33b1514ebac" name="s2" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="71" y1="288" x2="142" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="7abd8566-d1ef-40a6-95e3-7079a77155df" name="s3" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="142" y1="288" x2="213" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="646ab72c-9789-4271-8fa9-e8d416c0dde2" name="s4" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="213" y1="288" x2="284" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="635bb570-ae3b-4789-b519-1e2bde2efd2f" name="s5" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="284" y1="288" x2="355" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="8bb9387c-1e83-47b5-916c-d606cd68dec3" name="s6" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="355" y1="288" x2="426" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="481c9b0a-76b8-43d0-add7-e76075b61a9b" name="s7" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="426" y1="288" x2="497" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="6d452f50-9a23-43ed-afa2-0dfaf51b9847" name="s8" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="497" y1="288" x2="568" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="7e1e4dc6-176a-4a3f-82c8-1079d5662375" name="s9" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="568" y1="288" x2="639" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="9aad43f8-e1fd-49a8-9f80-e533bc9e57d9" name="s10" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="639" y1="288" x2="710" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="c90327e4-cd6e-4dde-83c2-1723739225f4" name="s11" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="710" y1="288" x2="781" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="3b7a097a-c1f4-4190-ae55-d189a19978c6" name="s12" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="781" y1="288" x2="852" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="00d3d8c0-a9d7-464e-a4fd-14c23f94184b" name="s13" layers="69229e15-6b70-464b-806b-8897329438a2" friendly="" x1="852" y1="288" x2="923" y2="384"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="c89dfa47-5c49-4c56-836b-19b7dd6e5651" name="deck_1" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="923" y1="0" x2="994" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="9e231907-746e-466c-b50d-1eb3db576f7f" name="deck_2" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="994" y1="0" x2="1065" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="492b2a75-4ed9-431b-9c7f-c93330a33eb6" name="deck_3" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1065" y1="0" x2="1136" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="e98cff41-88ec-47b0-bbd5-e684c97dc483" name="deck_4" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1136" y1="0" x2="1207" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="c6c3c2d2-620d-4fdd-9fce-bc6190817c12" name="deck_5" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1207" y1="0" x2="1278" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="b38f68aa-20ee-4be7-9840-136b838f7d8f" name="deck_6" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1278" y1="0" x2="1349" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="8c8ed8dd-8d4e-4c70-a267-ba27d105b572" name="deck_7" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1349" y1="0" x2="1420" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a4ea2b81-8403-4b5a-8413-e54da2e9b147" name="deck_8" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1420" y1="0" x2="1491" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="0e995a0b-0ab6-42e7-9ed9-d4900cfbe304" name="deck_9" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1491" y1="0" x2="1562" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000197-ea3d-4912-9e83-cddddaf0d7dd" name="deck_10" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1562" y1="0" x2="1633" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000198-ea3d-4912-9e83-cddddaf0d7dd" name="deck_11" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1633" y1="0" x2="1704" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000199-ea3d-4912-9e83-cddddaf0d7dd" name="deck_12" layers="040cb294-c4ad-47a5-abbb-6a7c8d1f3482" friendly="" x1="1704" y1="0" x2="1775" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000200-ea3d-4912-9e83-cddddaf0d7dd" name="foundation" layers="69229e15-6b70-464b-806b-889732943899" friendly="" x1="1775" y1="0" x2="1846" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000200-ea3d-4912-9e83-cddddaf0d7de" name="talon_restart" layers="69229e15-6b70-464b-806b-889732943899" friendly="" x1="1846" y1="0" x2="1917" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="25000200-ea3d-4912-9e83-cddddaf0d7df" name="talon_end" layers="69229e15-6b70-464b-806b-889732943899" friendly="" x1="1917" y1="0" x2="1988" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="8eb12cd0-f6ca-4f8b-9d38-50ec2945dc41" name="backside_empty" layers="69229e15-6b70-464b-806b-889732943899" friendly="" x1="1988" y1="0" x2="2059" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="5a0c5e61-f5b6-4db7-a2a9-405e5fe3c039" name="poker_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2059" y1="0" x2="2091" y2="22"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="1a6307f6-0806-496f-9b4f-afbd7cbd7bba" name="poker_2" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2091" y1="0" x2="2123" y2="22"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="b1ea9525-0e4e-4fc8-8a63-55b2bcf77a95" name="castle_0" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2123" y1="0" x2="2149" y2="12"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="1e9ff1c3-ee06-4142-90f5-ec081d91060b" name="robot_0" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2149" y1="0" x2="2173" y2="7"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="2fc57747-aa27-41d5-b348-ed2be68fa384" name="robot_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2173" y1="0" x2="2197" y2="7"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="a0531693-d6c6-4c30-98da-00dfe117c8b2" name="sun_0" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2197" y1="0" x2="2211" y2="12"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="9200227b-5948-4d98-b64a-17846b71b717" name="sun_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2211" y1="0" x2="2225" y2="12"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="fdf06b20-0845-4acd-9661-9d01d8efee4a" name="deck_7_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2225" y1="0" x2="2296" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="bc2a584b-5149-40cb-83d0-a5e67a69f9a6" name="deck_7_2" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2296" y1="0" x2="2367" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="90e8f2f4-80e9-46f3-b56c-f60c072aed88" name="deck_10_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2367" y1="0" x2="2438" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="4e0b645a-38fa-425e-bedb-c61c1061f714" name="deck_11_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2438" y1="0" x2="2509" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="722fa391-4a49-4ddf-b954-4a189cd2f562" name="deck_11_2" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2509" y1="0" x2="2580" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="e2ca4158-d3ec-468b-9afd-316d2e160587" name="deck_12_1" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2580" y1="0" x2="2651" y2="96"/>
generated.TileMapData
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root identifier="b4d443c9-6606-44df-a294-a3a78e57bad2" name="deck_12_2" layers="08b388db-1b65-4952-8dff-8664adacf4c8" friendly="" x1="2651" y1="0" x2="2722" y2="96"/>
================================================
FILE: properties/resources/Localization.csv
================================================
key,en_ca
About,About Solitaire
AboutMessage,Step into a nostalgic world with this homage to the classic Solitaire from Windows 95. Experience the same addictive gameplay now refined for a flawless journey. Immerse yourself in the timeless charm of this beloved card game brought back to life for new and old players alike.
Title,Solitaire
Game,Game
GameIcon,content/solitaire_logo.png
GameOver,Deal Again?
GameOverHeader,Solitaire
GitHub,https://github.com/danielricci/solitaire
Help,Help
Deal,Deal
Undo,Undo
Deck,Deck...
Options,Options...
Exit,Exit
GameWonStatusBar, Bonus: %s Press Esc or a mouse button to stop...
ScoreTitle, Score:
================================================
FILE: properties/resources/LocalizationStrings.java
================================================
/**
* MIT License
*
* Copyright (c) 2019 Daniel Ricci {@literal }
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package resources;
/**
* The list of available keys for localization lookup
*
* @author {@literal Daniel Ricci {@literal }}
*
*/
public class LocalizationStrings {
public static String ABOUT = "About";
public static String ABOUT_MESSAGE = "AboutMessage";
public static String TITLE = "Title";
public static String GAME = "Game";
public static String GAME_ICON = "GameIcon";
public static String GAME_OVER = "GameOver";
public static String GAME_OVER_HEADER = "GameOverHeader";
public static String GAME_WON_STATUS_BAR = "GameWonStatusBar";
public static String GITHUB = "GitHub";
public static String HELP = "Help";
public static String DEAL = "Deal";
public static String UNDO = "Undo";
public static String DECK = "Deck";
public static String OPTIONS = "Options";
public static String EXIT = "Exit";
public static String SCORE_TITLE = "ScoreTitle";
}
================================================
FILE: src/game/application/Game.java
================================================
package game.application;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.List;
import javax.swing.AbstractButton;
import javax.swing.UIManager;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.navigation.MenuBuilder;
import framework.core.system.Application;
import framework.core.system.EngineProperties;
import framework.core.system.EngineProperties.Property;
import framework.utils.globalisation.Localization;
import game.config.OptionsPreferences;
import game.menu.AboutMenuItem;
import game.menu.DeckMenuItem;
import game.menu.ExitMenuItem;
import game.menu.GitHubMenuItem;
import game.menu.NewGameMenuItem;
import game.menu.OnTopMenuItem;
import game.menu.OptionsMenuItem;
import game.menu.UndoMenuItem;
import game.views.FoundationPileView;
import game.views.GameView;
import game.views.TableauPileView;
import game.views.TalonPileView;
import game.views.helpers.DeckAnimationHelper;
import game.views.helpers.WinAnimationHelper;
import resources.LocalizationStrings;
/**
* @author Daniel Ricci {@literal }
*/
public final class Game extends Application {
/**
* Constructs a new instance of this class type
*
* @param isDebug The debug mode flag
*/
private Game(boolean isDebug) {
super(isDebug);
setMinimumSize(new Dimension(620, 436));
OptionsPreferences options = new OptionsPreferences();
options.load();
setLocationRelativeTo(null);
setAlwaysOnTop(options.alwaysOnTop);
setIconImage(Localization.instance().getLocalizedData(LocalizationStrings.GAME_ICON));
if(isDebug) {
addKeyListener(new KeyAdapter() {
@Override public void keyPressed(KeyEvent event) {
ViewFactory viewFactory = AbstractFactory.getFactory(ViewFactory.class);
if(event.getKeyCode() == KeyEvent.VK_F1) {
event.consume();
OptionsPreferences options = new OptionsPreferences();
options.load();
System.out.println(options);
System.out.println(viewFactory.get(TalonPileView.class).toString());
List pileViews = viewFactory.getAll(TableauPileView.class);
for(int i = pileViews.size() - 1; i >= 0; --i) {
System.out.println(pileViews.get(i));
}
List foundationViews = viewFactory.getAll(FoundationPileView.class);
for(int i = foundationViews.size() - 1; i >= 0; --i) {
System.out.println(foundationViews.get(i));
}
}
else if(event.getKeyCode() == KeyEvent.VK_F3) {
event.consume();
System.out.println(viewFactory.get(TalonPileView.class).toString());
}
}
});
}
addKeyListener(new KeyAdapter() {
@Override public void keyReleased(KeyEvent event) {
boolean _locked = false;
//Alt + Shift + 2
if(event.getKeyCode() == KeyEvent.VK_2 && event.getModifiersEx() == (KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)) {
if(!_locked) {
_locked = true;
GameView.forceGameWin();
_locked = false;
}
}
}
});
}
/**
* Main entry-point method
*
* @param args The arguments associated to the application entry point
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override public void run() {
boolean debugMode = false;
for(String arg : args) {
switch(arg.trim()) {
case "debug": {
debugMode = true;
break;
}
}
}
if(debugMode) {
EngineProperties.instance().setProperty(Property.LOG_DIRECTORY, System.getProperty("user.dir") + File.separator);
EngineProperties.instance().setProperty(Property.ENGINE_OUTPUT, Boolean.toString(true));
EngineProperties.instance().setProperty(Property.DISPLAY_EXCEPTIONS, Boolean.toString(true));
}
EngineProperties.instance().setProperty(Property.DATA_PATH_XML, "/generated/tilemap.xml");
EngineProperties.instance().setProperty(Property.DATA_PATH_SHEET, "/generated/tilemap.png");
EngineProperties.instance().setProperty(Property.LOCALIZATION_PATH_CVS, "/resources/Localization.csv");
EngineProperties.instance().setProperty(Property.SUPPRESS_SIGNAL_REGISTRATION_OUTPUT, Boolean.toString(false));
EngineProperties.instance().setProperty(Property.DISABLE_TRANSLATIONS_PLACEHOLDER, Boolean.toString(!debugMode));
Game game = new Game(debugMode);
game.setVisible(true);
}
});
}
@Override public void onRestart() {
super.onRestart();
if(AbstractFactory.isRunning()) {
// Clear the factory of it's contents
AbstractFactory.clearFactories();
// Remove everything from the application UI
Application.instance.getContentPane().removeAll();
// Indicate that the application is no longer in a restart state
isRestarting = false;
}
WinAnimationHelper.clear();
DeckAnimationHelper.getInstance().clear();
// Spawn a new game view and render its contents
GameView gameView = AbstractFactory.getFactory(ViewFactory.class).add(new GameView(), true);
instance.setContentPane(gameView);
gameView.render();
}
@Override public void windowOpened(WindowEvent windowEvent) {
System.out.println("Window Opened");
super.windowOpened(windowEvent);
// Set the title
setTitle(Localization.instance().getLocalizedString(LocalizationStrings.TITLE));
// Always show mnemonics in the menu system
UIManager.put("Button.showMnemonics", Boolean.TRUE);
// Game Menu
MenuBuilder.start(getJMenuBar())
.addMenu(Localization.instance().getLocalizedString(LocalizationStrings.GAME), KeyEvent.VK_G)
.addMenuItem(NewGameMenuItem.class)
.addSeparator()
.addMenuItem(UndoMenuItem.class)
.addMenuItem(DeckMenuItem.class)
.addMenuItem(OptionsMenuItem.class)
.addSeparator()
.addMenuItem(ExitMenuItem.class);
// Extras
MenuBuilder.start(getJMenuBar())
.addMenu("Extras")
.addMenuItem(OnTopMenuItem.class);
// Help Menu
MenuBuilder.start(getJMenuBar())
.addMenu(Localization.instance().getLocalizedString(LocalizationStrings.HELP), KeyEvent.VK_H)
.addMenuItem(GitHubMenuItem.class)
.addSeparator()
.addMenuItem(AboutMenuItem.class);
// Perform a new game programmatically
MenuBuilder.search(getJMenuBar(), NewGameMenuItem.class).getComponent(AbstractButton.class).doClick();
}
}
================================================
FILE: src/game/config/OptionsPreferences.java
================================================
package game.config;
import java.util.logging.Level;
import framework.core.system.GamePreferences;
import framework.utils.logging.Tracelog;
import game.views.OptionsDialogView;
import generated.DataLookup;
public final class OptionsPreferences extends GamePreferences {
public enum DrawOption { ONE, THREE };
public enum ScoringOption { STANDARD, VEGAS, NONE };
public boolean timedGame;
public boolean statusBar;
public boolean outlineDragging;
public boolean cumulativeScore;
public DrawOption drawOption;
public ScoringOption scoringOption;
public DataLookup.BACKSIDES deck;
public boolean alwaysOnTop;
public OptionsPreferences() {
super(OptionsDialogView.class);
}
@Override public void load() {
drawOption = DrawOption.values()[preferences.getInt("drawOption", DrawOption.ONE.ordinal())];
scoringOption = ScoringOption.values()[preferences.getInt("scoringOption", ScoringOption.STANDARD.ordinal())];
timedGame = preferences.getBoolean("timedGame", false);
statusBar = preferences.getBoolean("statusBar", false);
outlineDragging = preferences.getBoolean("outlineDragging", false);
cumulativeScore = preferences.getBoolean("cumulativeScore", false);
deck = DataLookup.BACKSIDES.values()[preferences.getInt("deck", DataLookup.BACKSIDES.DECK_1.ordinal())];
alwaysOnTop = preferences.getBoolean("alwaysOnTop", false);
}
@Override public void save() {
try {
preferences.putInt("drawOption", drawOption.ordinal());
preferences.putInt("scoringOption", scoringOption.ordinal());
preferences.putBoolean("timedGame", timedGame);
preferences.putBoolean("statusBar", statusBar);
preferences.putBoolean("outlineDragging", outlineDragging);
preferences.putBoolean("cumulativeScore", cumulativeScore);
preferences.putInt("deck", deck.ordinal());
preferences.putBoolean("alwaysOnTop", alwaysOnTop);
preferences.flush();
}
catch (Exception exception) {
Tracelog.log(Level.SEVERE, true, exception);
}
}
@Override public String toString() {
StringBuilder builder = new StringBuilder();
String header = "=========" + this.getClass().getSimpleName() + "=========";
builder.append(header + System.getProperty("line.separator"));
builder.append("Draw Option: " + drawOption + System.getProperty("line.separator"));
builder.append("Scoring Option: " + scoringOption + System.getProperty("line.separator"));
builder.append("Timed Game: " + Boolean.toString(timedGame) + System.getProperty("line.separator"));
builder.append("Status Bar: " + Boolean.toString(statusBar) + System.getProperty("line.separator"));
builder.append("Outline Dragging: " + Boolean.toString(outlineDragging) + System.getProperty("line.separator"));
builder.append("Cumulative Score: " + Boolean.toString(cumulativeScore) + System.getProperty("line.separator"));
builder.append("Deck: " + deck.toString() + System.getProperty("line.separator"));
builder.append("Always on Top: " + alwaysOnTop + System.getProperty("line.seperator"));
builder.append(new String(new char[header.length()]).replace("\0", "="));
return builder.toString();
}
}
================================================
FILE: src/game/controllers/CardController.java
================================================
package game.controllers;
import framework.core.mvc.controller.BaseController;
import game.entities.AbstractCardEntity;
import game.models.CardModel;
public class CardController extends BaseController {
/**
* The card model associated to this controller
*/
private final CardModel _card;
/**
* Constructs a new instance of this class type
*
* @param card The card model to associate to this controller
*/
public CardController(CardModel card) {
_card = card;
}
/**
* @return The card associated to this view
*/
public CardModel getCard() {
return _card;
}
/**
* Refreshes the content of the controller
*/
public void refresh() {
_card.refresh();
}
public boolean isKing() {
return _card.getCardEntity().isCardKing();
}
/**
* Verifies if the specified card provided can be used within this current card
*
* @param card The card to verify if it can be used to put over this card
*
* @return TRUE if this card is before the specified card and of the same suite, FALSE otherwise
*/
public boolean isValidFoundationMove(CardModel card) {
AbstractCardEntity thisCardEntity = _card.getCardEntity();
AbstractCardEntity cardEntity = card.getCardEntity();
return cardEntity.isSameSuite(thisCardEntity) && cardEntity.isCardRankedAfter(thisCardEntity);
}
}
================================================
FILE: src/game/controllers/MovementRecorderController.java
================================================
package game.controllers;
import java.util.logging.Level;
import framework.communication.internal.signal.ISignalListener;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.mvc.controller.BaseController;
import framework.utils.logging.Tracelog;
import game.models.MovementModel;
import game.models.MovementModel.MovementType;
import game.views.GameView;
import game.views.IUndoable;
/**
* The controller that handles recording of movement
*
* @author Daniel Ricci {@literal }
*/
public class MovementRecorderController extends BaseController {
/**
* The model representation of a movement
*/
private final MovementModel _movementModel = new MovementModel();
/**
* This flag indicates if the current state of the game can perform an undo
*/
private boolean _canUndo;
/**
* This flag indicates if this instance can record undo's.
*/
private boolean _lockRecording;
/**
* The last recorded `from` movement
*/
private IUndoable _source;
/**
* The last recorded `from` movement
*/
private IUndoable _destination;
/**
* Records the specified movement from one pile view implement to the other
*
* @param source The pile view implementation source
* @param destination The pile view implementation destination
*
*/
public void recordMovement(IUndoable source, IUndoable destination) {
// Do not proceed with the record movement if the lock is enabled
if(_lockRecording) {
return;
}
// Reset the values of this recorder
reset();
// Perform a backup on the source location
_source = source;
_source.performBackup();
_destination = destination;
MovementType fromMovement = MovementType.fromClass(source);
MovementType toMovement = MovementType.fromClass(destination);
//Tracelog.log(Level.INFO, true, String.format("Movement Detected: from [%s] to [%s]", fromMovement, toMovement));
if(fromMovement == MovementType.NONE || toMovement == MovementType.NONE) {
_canUndo = false;
}
else {
_canUndo = true;
}
// Update the model
_movementModel.setMovement(fromMovement, toMovement, false);
}
/**
* Performs an undo of the last recorded move
*/
public void undoLastMovement() {
if(!canUndo()) {
Tracelog.log(Level.SEVERE, true, "Cannot perform an undo");
return;
}
// Prevent recording undo's, to avoid performing an undo and have that movement recorded
_lockRecording = true;
// Undo the last action associated to the source
_source.undoLastAction();
// Update the model to notify listeners that a movement has occurred
_movementModel.setMovement(MovementType.fromClass(_source), MovementType.fromClass(_destination), true);
// Repaint the source and destination
AbstractFactory.getFactory(ViewFactory.class).get(GameView.class).repaint();
_source.getContainerClass().repaint();
_destination.getContainerClass().repaint();
// Reset the state of this recorder
reset();
// Enable back the lock
_lockRecording = false;
}
/**
* @return TRUE if an undo operation can be made, FALSE otherwise
*/
public boolean canUndo() {
return _canUndo;
}
/**
* Clears the undo availability
*/
public void clearUndo() {
_canUndo = false;
reset();
}
/**
* Resets the contents of this recorder
*/
private void reset() {
_canUndo = false;
if(_source != null) {
_source.clearBackup();
}
_source = null;
if(_destination != null) {
_destination.clearBackup();
}
_destination = null;
}
public void addSignalListener(ISignalListener listener) {
_movementModel.addListener(listener);
}
}
================================================
FILE: src/game/entities/AbstractCardEntity.java
================================================
package game.entities;
import java.awt.Image;
import java.util.UUID;
import framework.core.entity.DataEntity;
import generated.DataLookup.BACKSIDES;
import generated.DataLookup.LAYER;
public abstract class AbstractCardEntity extends DataEntity {
/**
* The backside entity associated to this card
*/
public final BacksideCardEntity backsideCardEntity = new BacksideCardEntity();
/**
* The layer associated to this entity
*/
protected final LAYER layer;
/**
* The UUID associated to the entity data
*/
protected final UUID identifier;
/**
* The ordinal associated to the entity data
*/
protected final int ordinal;
/**
* Constructs a new instance of this class type
*/
protected AbstractCardEntity() {
layer = null;
identifier = null;
ordinal = -1;
}
/**
* Constructs a new instance of this class type
*
* @param layer The layer to set this card entity to
* @param ordinal The oridinal position of the card
* @param the uuid associated to the card
*/
protected AbstractCardEntity(LAYER layer, int ordinal, UUID identifier) {
this.layer = layer;
this.identifier = identifier;
this.ordinal = ordinal;
setActiveData(identifier);
}
/**
* Indicates if this card is before the one specified.
*
* Note: This method is suite agnostic
*
* @param card The card to check rank against
*
* @return TRUE if the card is ranked before the one specified, false otherwise
*/
public final boolean isCardRankedBefore(AbstractCardEntity card) {
return ordinal + 1 == card.ordinal;
}
/**
* Indicates if this card is after the one specified.
*
* Note: This method is suite agnostic
*
* @param card The card to check rank against
*
* @return TRUE if the card is ranked after the one specified, false otherwise
*/
public final boolean isCardRankedAfter(AbstractCardEntity card) {
return ordinal - 1 == card.ordinal;
}
/**
* @return TRUE if this card is an ACE, false otherwise
*/
public final boolean isAceCard() {
return ordinal == 0;
}
/**
* Indicates if this card's suite is opposite to the card specified. An opposite suite is one that is of different color
*
* @param card The abstract card entity
*
* @return TRUE if this card's suite is opposite to the one specified, FALSE otherwise
*/
public abstract boolean isOppositeSuite(AbstractCardEntity card);
/**
* Indicates if this card's suite is the same as the specified card
*
* @param card The card check suite against
*
* @return TRUE if this card and the card specified are of the SAME suit, FALSE otherwise
*/
public final boolean isSameSuite(AbstractCardEntity card) {
return card.layer.equals(layer);
}
/**
* Sets the visibility of the cards' backside
*
* @param isVisible TRUE if the backside of the card is visible, FALSE otherwise
*/
public final void setBacksideVisible(boolean isVisible) {
backsideCardEntity.setIsBacksideShowing(isVisible);
}
/**
* @return TRUE if the backside is visible, FALSE otherwise
*/
public boolean getBacksideVisible() {
return backsideCardEntity.getIsBacksideShowing();
}
/**
* @return TRUE if this card is a king, FALSE otherwise
*/
public final boolean isCardKing() {
return ordinal == 12;
}
@Override public void refresh() {
backsideCardEntity.refresh();
}
@Override public Image getRenderableContent() {
if(getBacksideVisible()) {
return backsideCardEntity.getRenderableContent();
}
return super.getRenderableContent();
}
@Override public String toString() {
if(layer != null) {
return (!getBacksideVisible() ? "[F]" : "[B]") + ("\t") + (ordinal + 1) + " of " + layer.toString();
}
return super.toString();
}
/**
* Sets the backside of this entity
*
* @param backside The backside to set this entity with
*/
public void setBackside(BACKSIDES backside) {
backsideCardEntity.setActiveData(backside.identifier);
}
}
================================================
FILE: src/game/entities/BacksideCardEntity.java
================================================
package game.entities;
import framework.core.entity.DataEntity;
import game.config.OptionsPreferences;
import generated.DataLookup;
public class BacksideCardEntity extends DataEntity {
public static final String DECK_BACKSIDE_CHANGED = "DECK_BACKSIDE_CHANGED";
private boolean isBacksideVisible = false;
private DataLookup.BACKSIDES _backside;
public BacksideCardEntity() {
this.setBackside();
}
public void setIsBacksideShowing(boolean isShowing) {
isBacksideVisible = isShowing;
}
public boolean getIsBacksideShowing() {
return isBacksideVisible;
}
public DataLookup.BACKSIDES getBacksideData() {
return _backside;
}
private void setBackside() {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if (_backside == preferences.deck) {
return;
}
_backside = preferences.deck;
super.setActiveData(preferences.deck.identifier);
}
@Override public void refresh() {
setBackside();
}
}
================================================
FILE: src/game/entities/ClubCardEntity.java
================================================
package game.entities;
import generated.DataLookup;
import generated.DataLookup.CLUBS;
import generated.DataLookup.LAYER;
public final class ClubCardEntity extends AbstractCardEntity {
public ClubCardEntity(CLUBS card) {
super(DataLookup.LAYER.CLUBS, card.ordinal(), card.identifier);
}
@Override public boolean isOppositeSuite(AbstractCardEntity card) {
return card.layer == LAYER.HEARTS || card.layer == LAYER.DIAMONDS;
}
}
================================================
FILE: src/game/entities/DiamondCardEntity.java
================================================
package game.entities;
import generated.DataLookup;
import generated.DataLookup.LAYER;
public final class DiamondCardEntity extends AbstractCardEntity {
public DiamondCardEntity(DataLookup.DIAMONDS card) {
super(DataLookup.LAYER.DIAMONDS, card.ordinal(), card.identifier);
}
@Override public boolean isOppositeSuite(AbstractCardEntity card) {
return card.layer == LAYER.SPADES || card.layer == LAYER.CLUBS;
}
}
================================================
FILE: src/game/entities/FoundationCardEntity.java
================================================
package game.entities;
import generated.DataLookup;
public final class FoundationCardEntity extends BacksideCardEntity {
public FoundationCardEntity() {
setActiveData(DataLookup.MISC.FOUNDATION.identifier);
}
}
================================================
FILE: src/game/entities/HeartCardEntity.java
================================================
package game.entities;
import generated.DataLookup;
import generated.DataLookup.LAYER;
public final class HeartCardEntity extends AbstractCardEntity {
public HeartCardEntity(DataLookup.HEARTS card) {
super(DataLookup.LAYER.HEARTS, card.ordinal(), card.identifier);
}
@Override public boolean isOppositeSuite(AbstractCardEntity card) {
return card.layer == LAYER.SPADES || card.layer == LAYER.CLUBS;
}
}
================================================
FILE: src/game/entities/NullCardEntity.java
================================================
package game.entities;
public final class NullCardEntity extends AbstractCardEntity {
public NullCardEntity() {
backsideCardEntity.setIsBacksideShowing(true);
}
@Override public boolean isOppositeSuite(AbstractCardEntity card) {
return false;
}
}
================================================
FILE: src/game/entities/SpadeCardEntity.java
================================================
package game.entities;
import generated.DataLookup;
import generated.DataLookup.LAYER;
public final class SpadeCardEntity extends AbstractCardEntity {
public SpadeCardEntity(DataLookup.SPADES card) {
super(DataLookup.LAYER.SPADES, card.ordinal(), card.identifier);
}
@Override public boolean isOppositeSuite(AbstractCardEntity card) {
return card.layer == LAYER.HEARTS || card.layer == LAYER.DIAMONDS;
}
}
================================================
FILE: src/game/entities/StockCardEntity.java
================================================
package game.entities;
import generated.DataLookup;
public final class StockCardEntity extends AbstractCardEntity {
public static int identifier = 0;
public int identity = ++identifier;
public StockCardEntity() {
setBacksideVisible(true);
}
public void enableTalonRecycled() {
setActiveData(DataLookup.MISC.TALON_RESTART.identifier);
setBacksideVisible(false);
}
public void enableTalonEnd() {
setActiveData(DataLookup.MISC.TALON_END.identifier);
setBacksideVisible(false);
}
@Override public void refresh() {
super.refresh();
}
@Override public boolean isOppositeSuite(AbstractCardEntity card) {
return false;
}
@Override public String toString() {
return "Identity: " + identity;
}
}
================================================
FILE: src/game/menu/AboutMenuItem.java
================================================
package game.menu;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.EventObject;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.navigation.AbstractMenuItem;
import framework.core.system.Application;
import framework.utils.globalisation.Localization;
import game.views.StatusBarView;
import resources.LocalizationStrings;
/**
* The about menu item that displays information about the application
*
* @author Daniel Ricci {@literal }
*
*/
public class AboutMenuItem extends AbstractMenuItem {
/**
* Constructs a new instance of this class type
*
* @param parent The parent associated to this menu item
*/
public AboutMenuItem(JComponent parent) {
super(new JMenuItem(Localization.instance().getLocalizedString(LocalizationStrings.ABOUT)), parent);
super.getComponent(JMenuItem.class).setMnemonic(KeyEvent.VK_A);
}
@Override protected void onEntered(EventObject event) {
super.onEntered(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).setMenuDescription("About Solitaire");
}
@Override protected void onExited(EventObject event) {
super.onExited(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).clearMenuDescription();
}
@Override public void onExecute(ActionEvent actionEvent) {
JTextArea textArea = new JTextArea(8, 20);
textArea.setText(Localization.instance().getLocalizedString(LocalizationStrings.ABOUT_MESSAGE));
textArea.setEditable(false);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setBackground(UIManager.getColor("OptionsPane.background"));
JScrollPane scrollPane = new JScrollPane(textArea);
scrollPane.setBorder(null);
JPanel panel = new JPanel();
panel.add(scrollPane);
// Create a custom dialog
JOptionPane.showMessageDialog(
Application.instance,
panel,
Localization.instance().getLocalizedString(LocalizationStrings.ABOUT),
JOptionPane.INFORMATION_MESSAGE);
}
}
================================================
FILE: src/game/menu/DeckMenuItem.java
================================================
package game.menu;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.EventObject;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import framework.communication.internal.signal.arguments.EventArgs;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ModelFactory;
import framework.core.factories.ViewFactory;
import framework.core.navigation.AbstractMenuItem;
import framework.utils.globalisation.Localization;
import game.config.OptionsPreferences;
import game.entities.BacksideCardEntity;
import game.models.CardModel;
import game.views.DeckSelectionDialogView;
import game.views.StatusBarView;
import game.views.StockView;
import resources.LocalizationStrings;
/**
* Menu item for starting a new game
*
* @author Daniel Ricci {@literal }
*
*/
public class DeckMenuItem extends AbstractMenuItem {
/**
* Constructs a new instance of this class type
*
* @param parent The parent associated to this menu item
*/
public DeckMenuItem(JComponent parent) {
super(new JMenuItem(Localization.instance().getLocalizedString(LocalizationStrings.DECK)), parent);
super.getComponent(JMenuItem.class).setMnemonic(KeyEvent.VK_C);
}
@Override protected void onEntered(EventObject event) {
super.onEntered(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).setMenuDescription("Choose new deck back");
}
@Override protected void onExited(EventObject event) {
super.onExited(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).clearMenuDescription();
}
@Override public void onExecute(ActionEvent actionEvent) {
// Clear the description when the execution has occurred. This is so that the description does not stay
// stuck until the dialog has closed
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).clearMenuDescription();
DeckSelectionDialogView view = new DeckSelectionDialogView();
view.render();
if(view.getDialogResult() == JOptionPane.OK_OPTION) {
// Update all the backsides of all the cards in the game
EventArgs args = new EventArgs(this, CardModel.EVENT_UPDATE_BACKSIDE);
AbstractFactory.getFactory(ModelFactory.class).multicastSignalListeners(CardModel.class, args);
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
// Send out the signal to the stockview
AbstractFactory.getFactory(ViewFactory.class).multicastSignalListeners(StockView.class, new EventArgs(this, BacksideCardEntity.DECK_BACKSIDE_CHANGED));
}
}
}
================================================
FILE: src/game/menu/ExitMenuItem.java
================================================
package game.menu;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.util.EventObject;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.navigation.AbstractMenuItem;
import framework.core.system.Application;
import framework.utils.globalisation.Localization;
import game.views.StatusBarView;
/**
* Menu item for exiting the game
*
* @author Daniel Ricci {@literal }
*
*/
public class ExitMenuItem extends AbstractMenuItem {
/**
* Constructs a new instance of this class type
*
* @param parent The parent associated to this menu item
*/
public ExitMenuItem(JComponent parent) {
super(new JMenuItem(Localization.instance().getLocalizedString("Exit")), parent);
super.getComponent(JMenuItem.class).setMnemonic(KeyEvent.VK_X);
}
@Override protected void onEntered(EventObject event) {
super.onEntered(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).setMenuDescription("Exit Solitaire");
}
@Override protected void onExited(EventObject event) {
super.onExited(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).clearMenuDescription();
}
@Override public void onExecute(ActionEvent actionEvent) {
Application.instance.dispatchEvent(new WindowEvent(Application.instance, WindowEvent.WINDOW_CLOSING));
}
}
================================================
FILE: src/game/menu/GitHubMenuItem.java
================================================
package game.menu;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.net.URI;
import java.util.EventObject;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.navigation.AbstractMenuItem;
import framework.utils.globalisation.Localization;
import game.views.StatusBarView;
import resources.LocalizationStrings;
public class GitHubMenuItem extends AbstractMenuItem {
public GitHubMenuItem(JComponent parent) {
super(new JMenuItem("GitHub Repository"), parent);
}
@Override protected void onEntered(EventObject event) {
super.onEntered(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).setMenuDescription("GitHub Repository - " + Localization.instance().getLocalizedString(LocalizationStrings.GITHUB));
}
@Override protected void onExited(EventObject event) {
super.onExited(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).clearMenuDescription();
}
@Override public void onExecute(ActionEvent actionEvent) {
String url = Localization.instance().getLocalizedString(LocalizationStrings.GITHUB);
try {
if (Desktop.isDesktopSupported()) {
Desktop desktop = Desktop.getDesktop();
desktop.browse(new URI(url));
} else {
Runtime.getRuntime().exec("cmd /c start " + url);
}
}
catch(Exception exception) {
exception.printStackTrace();
}
}
}
================================================
FILE: src/game/menu/NewGameMenuItem.java
================================================
package game.menu;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.EventObject;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.navigation.AbstractMenuItem;
import framework.core.system.Application;
import framework.utils.globalisation.Localization;
import game.views.StatusBarView;
/**
* Menu item for starting a new game
*
* @author Daniel Ricci {@literal }
*
*/
public class NewGameMenuItem extends AbstractMenuItem {
/**
* Constructs a new instance of this class type
*
* @param parent The parent associated to this menu item
*/
public NewGameMenuItem(JComponent parent) {
super(new JMenuItem(Localization.instance().getLocalizedString("Deal")), parent);
super.getComponent(JMenuItem.class).setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0, true));
super.getComponent(JMenuItem.class).setMnemonic(KeyEvent.VK_D);
}
@Override protected void onEntered(EventObject event) {
super.onEntered(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).setMenuDescription("Deal a new game");
}
@Override protected void onExited(EventObject event) {
super.onExited(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).clearMenuDescription();
}
@Override public void onExecute(ActionEvent actionEvent) {
Application.instance.onRestart();
}
}
================================================
FILE: src/game/menu/OnTopMenuItem.java
================================================
package game.menu;
import java.awt.event.ActionEvent;
import java.util.EventObject;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.navigation.AbstractMenuItem;
import framework.core.system.Application;
import framework.utils.globalisation.Localization;
import game.config.OptionsPreferences;
import game.views.StatusBarView;
/**
* The always-on-top menu item that keeps the game above all other applications
*
* @author Daniel Ricci {@literal }
*
*/
public class OnTopMenuItem extends AbstractMenuItem {
/**
* Constructs a new instance of this class type
*
* @param parent The parent associated to this menu item
*/
public OnTopMenuItem(JComponent parent) {
super(new JCheckBoxMenuItem(Localization.instance().getLocalizedString("Always on Top"), Application.instance.isAlwaysOnTop()), parent);
}
@Override protected void onEntered(EventObject event) {
super.onEntered(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).setMenuDescription("Sets this application to always show above other applications");
}
@Override protected void onExited(EventObject event) {
super.onExited(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).clearMenuDescription();
}
@Override protected boolean isEnabled() {
return super.isEnabled() && Application.instance.isAlwaysOnTopSupported();
}
@Override public void onExecute(ActionEvent actionEvent) {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
preferences.alwaysOnTop = !preferences.alwaysOnTop;
preferences.save();
Application.instance.setAlwaysOnTop(preferences.alwaysOnTop);
}
}
================================================
FILE: src/game/menu/OptionsMenuItem.java
================================================
package game.menu;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.EventObject;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import framework.communication.internal.signal.arguments.EventArgs;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.navigation.AbstractMenuItem;
import framework.core.navigation.MenuBuilder;
import framework.core.system.Application;
import framework.utils.globalisation.Localization;
import game.views.CardView;
import game.views.OptionsDialogView;
import game.views.StatusBarView;
import resources.LocalizationStrings;
/**
* Menu item for pulling up the options menu
*
* @author Daniel Ricci {@literal }
*
*/
public class OptionsMenuItem extends AbstractMenuItem {
/**
* Constructs a new instance of this class type
*
* @param parent The parent associated to this menu item
*/
public OptionsMenuItem(JComponent parent) {
super(new JMenuItem(Localization.instance().getLocalizedString(LocalizationStrings.OPTIONS)), parent);
super.getComponent(JMenuItem.class).setMnemonic(KeyEvent.VK_O);
}
@Override protected void onEntered(EventObject event) {
super.onEntered(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).setMenuDescription("Change Solitaire options");
}
@Override protected void onExited(EventObject event) {
super.onExited(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).clearMenuDescription();
}
@Override public void onExecute(ActionEvent actionEvent) {
// Clear the description when the execution has occured. This is so that the description does not stay
// stuck until the dialog has closed
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).clearMenuDescription();
OptionsDialogView options = new OptionsDialogView();
options.render();
if(options.getDialogResult() == JOptionPane.OK_OPTION) {
if(options.refreshGameRequired) {
Application.instance.isRestarting = true;
MenuBuilder.search(Application.instance.getJMenuBar(), NewGameMenuItem.class).getComponent(AbstractButton.class).doClick();
}
else {
if(options.statusBarChanged) {
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).synchronizeWithOptions();
}
if(options.outlineDraggingChanged) {
EventArgs args = new EventArgs(this, CardView.EVENT_OUTLINE_SYNCHRONIZE);
args.setSuppressUpdate(true);
AbstractFactory.getFactory(ViewFactory.class).multicastSignalListeners(CardView.class, args);
}
}
}
}
}
================================================
FILE: src/game/menu/UndoMenuItem.java
================================================
package game.menu;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.EventObject;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ControllerFactory;
import framework.core.factories.ViewFactory;
import framework.core.navigation.AbstractMenuItem;
import framework.utils.globalisation.Localization;
import game.controllers.MovementRecorderController;
import game.views.StatusBarView;
/**
* Menu item for starting a new game
*
* @author Daniel Ricci {@literal }
*
*/
public class UndoMenuItem extends AbstractMenuItem {
/**
* Constructs a new instance of this class type
*
* @param parent The parent associated to this menu item
*/
public UndoMenuItem(JComponent parent) {
super(new JMenuItem(Localization.instance().getLocalizedString("Undo")), parent);
super.getComponent(JMenuItem.class).setMnemonic(KeyEvent.VK_U);
}
@Override protected void onEntered(EventObject event) {
super.onEntered(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).setMenuDescription("Undo last action");
}
@Override protected void onExited(EventObject event) {
super.onExited(event);
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).clearMenuDescription();
}
@Override protected boolean isEnabled() {
return AbstractFactory.getFactory(ControllerFactory.class).get(MovementRecorderController.class).canUndo();
}
@Override public void onExecute(ActionEvent actionEvent) {
AbstractFactory.getFactory(ControllerFactory.class).get(MovementRecorderController.class).undoLastMovement();
}
}
================================================
FILE: src/game/models/CardModel.java
================================================
package game.models;
import java.awt.Image;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import framework.communication.internal.signal.ISignalReceiver;
import framework.communication.internal.signal.arguments.EventArgs;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ModelFactory;
import framework.core.mvc.model.BaseModel;
import game.config.OptionsPreferences;
import game.entities.AbstractCardEntity;
import game.entities.ClubCardEntity;
import game.entities.DiamondCardEntity;
import game.entities.HeartCardEntity;
import game.entities.SpadeCardEntity;
import generated.DataLookup;
public class CardModel extends BaseModel {
public static final String EVENT_UPDATE_BACKSIDE = "EVENT_UPDATE_BACKSIDE";
private final AbstractCardEntity _cardEntity;
public CardModel(AbstractCardEntity cardEntity) {
_cardEntity = cardEntity;
addSignal(EVENT_UPDATE_BACKSIDE, new ISignalReceiver() {
@Override public void signalReceived(EventArgs event) {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
_cardEntity.setBackside(preferences.deck);
refresh();
}
});
}
public boolean isEmpty() {
return _cardEntity == null;
}
public void setBackside(boolean backside) {
_cardEntity.setBacksideVisible(backside);
}
public boolean getIsBackside() {
return _cardEntity.getBacksideVisible();
}
public static List newInstances() {
List entities = new ArrayList();
ModelFactory factory = AbstractFactory.getFactory(ModelFactory.class);
for(DataLookup.HEARTS heart : DataLookup.HEARTS.values()) {
entities.add(factory.add(new CardModel(new HeartCardEntity(heart))));
}
for(DataLookup.CLUBS club : DataLookup.CLUBS.values()) {
entities.add(factory.add(new CardModel(new ClubCardEntity(club))));
}
for(DataLookup.DIAMONDS diamond : DataLookup.DIAMONDS.values()) {
entities.add(factory.add(new CardModel(new DiamondCardEntity(diamond))));
}
for(DataLookup.SPADES spade : DataLookup.SPADES.values()) {
entities.add(factory.add(new CardModel(new SpadeCardEntity(spade))));
}
Collections.shuffle(entities);
return entities;
}
public AbstractCardEntity getCardEntity() {
return _cardEntity;
}
/**
* Checks if the passed in card is ordinally before and of opposite suite to this card
* @param card The card
*
* @return TRUE if the card passed in is both ordinally before and of opposite suite to this card, FALSE otherwise
*/
public boolean isCardBeforeAndOppositeSuite(CardModel card) {
//System.out.println("Attempting to place " + card._cardEntity.toString() + " over " + _cardEntity.toString());
return !_cardEntity.getBacksideVisible() && !card._cardEntity.getBacksideVisible() && card._cardEntity.isOppositeSuite(_cardEntity) && card._cardEntity.isCardRankedBefore(_cardEntity);
}
@Override public String toString() {
return _cardEntity.toString();
}
@Override public Image getRenderableContent() {
if(_cardEntity == null) {
return null;
}
return _cardEntity.getRenderableContent();
}
}
================================================
FILE: src/game/models/MovementModel.java
================================================
package game.models;
import framework.core.mvc.model.BaseModel;
import game.views.FoundationPileView;
import game.views.StockView;
import game.views.TableauPileView;
import game.views.TalonPileView;
public class MovementModel extends BaseModel {
public enum MovementType {
STOCK,
TALON,
FOUNDATION,
TABLEAU,
NONE;
public static MovementType fromClass(Object clazz) {
if(clazz instanceof StockView) {
return MovementType.STOCK;
}
else if(clazz instanceof TalonPileView) {
return MovementType.TALON;
}
else if(clazz instanceof FoundationPileView) {
return MovementType.FOUNDATION;
}
else if(clazz instanceof TableauPileView) {
return MovementType.TABLEAU;
}
else {
return MovementType.NONE;
}
}
}
private MovementType _from;
private MovementType _to;
private boolean _isUndo;
public void setMovement(MovementType from, MovementType to, boolean isUndo) {
_from = from;
_to = to;
_isUndo = isUndo;
doneUpdating();
}
public MovementType getFrom() {
return _from;
}
public MovementType getTo() {
return _to;
}
public boolean getIsUndo() {
return _isUndo;
}
}
================================================
FILE: src/game/views/AbstractPileView.java
================================================
package game.views;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.JLayeredPane;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.mvc.view.PanelView;
/**
* Abstract representation of a pile view
*
* @author Daniel Ricci {@literal }
*/
public abstract class AbstractPileView extends PanelView implements IUndoable {
/**
* The layered pane that holds the list of cards
*/
protected final JLayeredPane layeredPane = new JLayeredPane();
/**
* The list of cards that were previously moved, used for undo purposes
*/
private final List _previousCards = new ArrayList();
/**
* Constructs a new instance of this class type
*/
protected AbstractPileView() {
setLayout(new BorderLayout());
setPreferredSize(new Dimension(CardView.CARD_WIDTH, this.getPreferredSize().height));
setOpaque(false);
add(layeredPane, BorderLayout.CENTER);
}
/**
* Removes the highlights from this view and its underlying cards
*/
public void removeHighlight() {
for(Component comp : layeredPane.getComponents()) {
CardView cardView = (CardView)comp;
cardView.setIsHighlighted(false);
}
setIsHighlighted(false);
repaint();
}
/**
* @return The last ordered card held within this pile view
*/
public CardView getLastCard() {
if(layeredPane.getComponentCount() == 0) {
return null;
}
Component comp = layeredPane.getComponents()[0];
if(!(comp instanceof CardView)) {
return null;
}
return (CardView)comp;
}
/**
* Adds the specified card view to this pile
*
* @param cardView The card to add to this pile
*
*/
public void addCard(CardView cardView) {
addCard(cardView, layeredPane.getComponents().length);
}
public void addCard(CardView cardView, int layerPosition) {
// Hold onto a reference of the parent for repainting reasons
Container parentCardView = cardView.getParent();
// Get the list of components associated to the card view.
// This list represents all the children associated to the said CardView.this reference.
List components = new ArrayList(Arrays.asList(cardView.layeredPane.getComponents()));
// Add the card view component, this will add it to the end
components.add(cardView);
// Reverse the list because layered panes associate objects closer to layer 0 as being closer to the screen.
Collections.reverse(components);
// Add the cards to this pile view
for(Component comp : components) {
layeredPane.add(comp);
layeredPane.setLayer(comp, layerPosition);
Point offset = getCardOffset((CardView)comp);
comp.setBounds(new Rectangle(offset.x, offset.y, comp.getPreferredSize().width, comp.getPreferredSize().height));
++layerPosition;
}
parentCardView.repaint();
repaint();
}
/**
* Gets the offset that should be set to the specified card view
*
* @param cardView The cardView
*
* @return The offset that this card should be at
*/
protected abstract Point getCardOffset(CardView cardView);
/**
* @return The components associated to the layered pane of this view, grouped by layer identifier.
*/
protected final List getComponentsGroupedByLayer() {
List components = new ArrayList();
for(int i = 0; i <= layeredPane.highestLayer(); ++i) {
Component[] comps = layeredPane.getComponentsInLayer(i);
if(comps.length > 0) {
components.add(layeredPane.getComponentsInLayer(i));
}
}
return components;
}
@Override public void undoLastAction() {
List componentsList = Arrays.asList(layeredPane.getComponents());
for(Component comp : _previousCards) {
if(!componentsList.contains(comp)) {
addCard((CardView) comp);
}
}
repaint();
}
@Override public void performBackup() {
// Get all the cards currently in the pile view
List allComponents = Arrays.asList(layeredPane.getComponents());
Collections.reverse(allComponents);
_previousCards.addAll(allComponents);
// Attempt to get the card that is currently being dragged.
// Note: This is only valid in a non-outline scenario. In this scenario, the card that we are getting
// here is the card that was clicked on. The subsequent cards that are being dragged along with it
// if any can be found in the layered pane associated to this clicked card.
CardView cardView = AbstractFactory.getFactory(ViewFactory.class).get(GameView.class).getCardComponent();
if(cardView != null) {
_previousCards.add(cardView);
List components = Arrays.asList(cardView.layeredPane.getComponents());
Collections.reverse(components);
// Add any subsequent cards below the clicked card if any
for(Component comp : components) {
_previousCards.add(comp);
}
}
}
@Override public void clearBackup() {
_previousCards.clear();
}
@Override public void render() {
super.render();
for(Component component : layeredPane.getComponents()) {
if(component instanceof CardView) {
CardView view = (CardView) component;
view.render();
}
}
repaint();
}
@Override public String toString() {
StringBuilder builder = new StringBuilder();
String header = "========" + this.getClass().getSimpleName().toUpperCase() + "========";
builder.append(header + System.getProperty("line.separator"));
for(Component comp : layeredPane.getComponents()) {
builder.append(comp + System.getProperty("line.separator"));
}
builder.append(new String(new char[header.length()]).replace("\0", "="));
return builder.toString();
}
}
================================================
FILE: src/game/views/CardOutlineView.java
================================================
package game.views;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.JLayeredPane;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ControllerFactory;
import framework.core.factories.ViewFactory;
import framework.core.mvc.view.PanelView;
import framework.core.mvc.view.layout.DragListener;
import framework.core.physics.CollisionListener;
import framework.core.physics.ICollidable;
import framework.utils.MouseListenerEvent;
import framework.utils.MouseListenerEvent.SupportedActions;
import game.controllers.MovementRecorderController;
import game.views.components.ExclusiveLineBorder;
/**
* This view represents the outline of a normal card view
*
* @author Daniel Ricci {@literal }
*
*/
public final class CardOutlineView extends PanelView {
/**
* The current bounds that have been set to this outline
*/
private Rectangle _bounds = null;
/**
* The card drag events for this outline view
*
* @author Daniel Ricci {@literal }
*
*/
private class CardDragEvents extends MouseMotionAdapter {
private PanelView _collidedView = null;
@Override public void mouseDragged(MouseEvent event) {
if(!isEnabled()) {
return;
}
// If we are dragging the mouse but the drag listener is not working properly go no further
if(!_dragListener.isDragging() || !_dragListener.getIsEnabled()) {
return;
}
ICollidable collider = _collisionListener.getCollision();
if(collider != null) {
// Determine what the collision was with. Either it was with a card, or
// it is with the Foundation or an empty PileView
PanelView collidedView = (PanelView)collider;
if(collidedView instanceof AbstractPileView) {
CardView card = ((AbstractPileView)collidedView).getLastCard();
if(card != null) {
collidedView = card;
}
}
// If there was something that was already collided with
// then remove the highlight
if(_collidedView != null) {
_collidedView.setIsHighlighted(false);
}
// Set the newly collided view
_collidedView = collidedView;
_collidedView.setIsHighlighted(true);
}
else {
if(_collidedView != null) {
_collidedView.setIsHighlighted(false);
}
_collidedView = null;
}
}
}
/**
* The card selection events for this outline view
*
* @author Daniel Ricci {@literal }
*
*/
private class CardSelectionEvents extends MouseListenerEvent {
public CardSelectionEvents() {
super(SupportedActions.LEFT);
}
@Override public void mousePressed(MouseEvent event) {
super.mousePressed(event);
if(event.isConsumed()) {
return;
}
if(!isEnabled()) {
return;
}
// Indicates if the auto move had occurred successfully
boolean hasAutomoveWorked = false;
// If the click count is two then perform the double click event of the underlying card
// and go no further. This will handle cases where the outline mode is enabled, and the user
// double clicks to put a potential card in the foundation
if(event.getClickCount() == 2) {
// Get a reference to the parent of the underlying card view
Component parent = _cardView.getParent();
// Perform the double click operation
hasAutomoveWorked = _cardView.performCardAutoMovement();
if(hasAutomoveWorked) {
// Stop the drag listener from dragging, this is because you could still drag when you have the
// second mouse down, and this causes the border outline to still render. This is also guard is other
// mouseDragged events in this class, where we check to see that the dragListener component is being dragged
_dragListener.stopDragEvent();
}
// Perform a repaint of the parent
parent.repaint();
}
if(!hasAutomoveWorked) {
// Get the list of components that are in the parent container
Component[] components = ((JLayeredPane) _cardView.getParent()).getComponents();
// Go through the list of cards until the one that is being dragged is found. This is done
// in a vanilla for loop so that we can take advantage of the index found
for(int i = components.length - 1; i >= 0; --i) {
if(components[i].equals(_cardView)) {
// Get the list of cardviews from the found card view that is being dragged to the end
List cardViews = Arrays.asList(Arrays.copyOfRange(components, 0, i, CardView[].class));
// Reverse the list so that when the iteration occurs, it uses the same ordering that is represente visually
Collections.reverse(cardViews);
// Fixes a bug where the layered pane is chopped from the waist down
_layeredPane.setSize(_layeredPane.getWidth(), _layeredPane.getHeight() + (cardViews.size() * 12));
// Go through the list of cards and add them to the layered pane within the outline
for(int j = 0; j < cardViews.size(); ++j) {
CardOutlineView outline = cardViews.get(j).getOutlineView();
_layeredPane.add(outline);
_layeredPane.setLayer(outline, j);
// The bounds here contains `-1` because I want the border to be perfectly overlapped
outline.setBounds(new Rectangle(-1, 12 * (j + 1), cardViews.get(j).getPreferredSize().width, cardViews.get(j).getPreferredSize().height + _layeredPane.getHeight()));
}
// Position the card at the same place where the drag was attempted from, because when you
// add to the application it will position the component at the origin which is not desired
Point initialLocation = _cardView.getLocation();
// Update the bounds of the card view
_bounds = new Rectangle(_cardView.getParent().getParent().getLocation().x + initialLocation.x + 1, _cardView.getParent().getParent().getLocation().y + initialLocation.y + 1, _layeredPane.getWidth(), _layeredPane.getHeight());
// Get a reference to the game view and status view, and add the card into the proper
// z-order so that it appears underneath the status bar, but over everything else in the game
ViewFactory viewFactory = AbstractFactory.getFactory(ViewFactory.class);
GameView gameView = viewFactory.get(GameView.class);
StatusBarView statusBarView = viewFactory.get(StatusBarView.class);
gameView.add(CardOutlineView.this, gameView.getComponentZOrder(statusBarView) + 1);
gameView.repaint();
// Do not continue iterating, the card was found so there is nothing left to do
break;
}
}
}
}
@Override public void mouseReleased(MouseEvent event) {
super.mouseReleased(event);
if(event.isConsumed()) {
return;
}
if(!isEnabled()) {
return;
}
ICollidable collider = _collisionListener.getCollision();
_bounds = null;
if(collider != null) {
// Get a reference to the pile view that has has been collided with
AbstractPileView pileViewCollider = (AbstractPileView) collider;
// Get the before movement type to know where the move is coming from
AbstractPileView fromPileView = (AbstractPileView) _cardView.getParent().getParent();
// Record that the movement occurred
AbstractFactory.getFactory(ControllerFactory.class).get(MovementRecorderController.class).recordMovement(fromPileView, pileViewCollider);
// Unselect all the cards within this pile view to remove the outline xor'd highlight
pileViewCollider.removeHighlight();
// Repaint the pile view
pileViewCollider.repaint();
// Add this card to the new location
int initialSize = pileViewCollider.layeredPane.getComponents().length;
pileViewCollider.layeredPane.add(_cardView);
pileViewCollider.layeredPane.setLayer(_cardView, initialSize);
Point offset = pileViewCollider.getCardOffset(_cardView);
_cardView.setBounds(new Rectangle(offset.x, offset.y, _cardView.getPreferredSize().width, _cardView.getPreferredSize().height));
// Increment the initial size to prepare for the other cards to be inserted
++initialSize;
// Get the list of layered components and go through each of them, adding each one
// to the proper destination
List layeredComponents = Arrays.asList(_layeredPane.getComponents());
Collections.reverse(layeredComponents);
for(int i = 0; i < layeredComponents.size(); ++i) {
CardOutlineView outline = (CardOutlineView) layeredComponents.get(i);
pileViewCollider.layeredPane.add(outline._cardView);
pileViewCollider.layeredPane.setLayer(outline._cardView, initialSize + i);
outline._cardView.add(outline);
outline.setBorder(null);
Point offsetOutline = pileViewCollider.getCardOffset(outline._cardView);
outline._cardView.setBounds(new Rectangle(offsetOutline.x, offsetOutline.y, outline._cardView.getPreferredSize().width, outline._cardView.getPreferredSize().height));
}
_layeredPane.removeAll();
pileViewCollider.repaint();
}
else {
Component[] layeredComponents = _layeredPane.getComponents();
for(int i = 0; i < layeredComponents.length; ++i) {
CardOutlineView outline = (CardOutlineView) layeredComponents[i];
outline._cardView.add(outline);
outline.setBorder(null);
}
}
// Add the this outline back to it's underlying card view
CardOutlineView.this.setBorder(null);
_cardView.add(CardOutlineView.this);
// Repaint the components involved
ViewFactory viewFactory = AbstractFactory.getFactory(ViewFactory.class);
GameView gameView = viewFactory.get(GameView.class);
gameView.repaint();
_cardView.getParent().repaint();
// Set the drag listener to be enabled. This is because it could have been disabled from other workflows, however if
// the code got this far it should be re-enabled
_dragListener.setEnabled(true);
// See if the board is in a winning state
GameView.scanGameForWin();
}
}
/**
* The layered pane that holds the potential list of cards that would be dragged along-side this card vuew
*/
private final JLayeredPane _layeredPane = new JLayeredPane();
/**
* The draggable listener associated to this view
*/
private final DragListener _dragListener = new DragListener(SupportedActions.LEFT);
/**
* The collision listener associated to this view
*/
private final CollisionListener _collisionListener = new CollisionListener(this, SupportedActions.LEFT);
/**
* The card view associated to this outline
*/
private final CardView _cardView;
/**
* The border layout associated to this view
*/
private final Border _border = new ExclusiveLineBorder(1);
/**
* Constructs a new instance of this class type
*
* @param cardView The card view to associate to this outline
*/
public CardOutlineView(CardView cardView) {
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setPreferredSize(new Dimension(CardView.CARD_WIDTH, CardView.CARD_HEIGHT));
setOpaque(false);
add(_layeredPane);
addMouseListener(_dragListener);
addMouseMotionListener(_dragListener);
addMouseListener(_collisionListener);
addMouseMotionListener(_collisionListener);
addMouseListener(new CardSelectionEvents());
addMouseMotionListener(new CardDragEvents());
// Set the controller of this outline to the same controller of the specified card
_cardView = cardView;
getViewProperties().setEntity(cardView.getViewProperties().getEntity());
addMouseListener(new MouseAdapter() {
@Override public void mousePressed(MouseEvent event) {
if(!SwingUtilities.isRightMouseButton(event)) {
AbstractFactory.getFactory(ViewFactory.class).get(TimerView.class).startGameTimer();
removeMouseListener(this);
}
}
});
MouseListenerEvent mle = new MouseListenerEvent(SupportedActions.LEFT) {
@Override public void mouseDragged(MouseEvent event) {
super.mouseDragged(event);
// 1. Event is consumed
// 2. This card outline is not in an enabled state
// 3. The drag listener component is not in the state of being dragged
// 4. The drag listener is not enabled
if(event.isConsumed() || !isEnabled() || !_dragListener.isDragging() || !_dragListener.getIsEnabled()) {
return;
}
// If the border has not yet been set
if(getBorder() != _border) {
// Set this border of this outline
setBorder(_border);
// Go through the list of outlines owned by this outline and show their borders
// Note: On mouse release should handle removing the borders set withint his drag event
for(Component component : _layeredPane.getComponents()) {
if(component instanceof CardOutlineView) {
CardOutlineView outline = ((CardOutlineView)component);
outline.setBorder(outline._border);
}
}
}
}
};
addMouseListener(mle);
addMouseMotionListener(mle);
}
@Override public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
// Propagate the state change to the card view. Ensure that no circular reference could ever occur
if(_cardView.isEnabled() != enabled) {
_cardView.setEnabled(enabled);
}
_dragListener.setEnabled(enabled);
_collisionListener.setEnabled(enabled);
}
@Override public void setBounds(int x, int y, int width, int height) {
//System.out.printf("(%d,%d,%d%d)\n", x, y, width, height);
// Prevent issues related to the view being updated because of other components, causing this component
// to be position improperly.
if(_bounds != null && !_dragListener.isDragging()) {
super.setBounds(_bounds.x, _bounds.y, _bounds.width, _bounds.height);
}
else if(y != 6) {
super.setBounds(x, y, width, height);
}
}
}
================================================
FILE: src/game/views/CardView.java
================================================
package game.views;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import javax.swing.BoxLayout;
import javax.swing.JLayeredPane;
import javax.swing.SwingUtilities;
import framework.api.IView;
import framework.communication.internal.signal.ISignalReceiver;
import framework.communication.internal.signal.arguments.EventArgs;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ControllerFactory;
import framework.core.factories.ViewFactory;
import framework.core.graphics.IRenderable;
import framework.core.mvc.view.PanelView;
import framework.core.mvc.view.layout.DragListener;
import framework.core.physics.CollisionListener;
import framework.core.physics.ICollidable;
import framework.utils.MouseListenerEvent;
import framework.utils.MouseListenerEvent.SupportedActions;
import framework.utils.logging.Tracelog;
import game.config.OptionsPreferences;
import game.config.OptionsPreferences.DrawOption;
import game.controllers.CardController;
import game.controllers.MovementRecorderController;
import game.models.CardModel;
import game.views.helpers.ViewHelper;
/**
* This view represents the representation of a single card
*
* @author Daniel Ricci {@literal }
*
*/
public final class CardView extends PanelView implements ICollidable {
/**
* The card selection events for this outline view
*
* @author Daniel Ricci {@literal }
*
*/
private final class CardSelectionEvents extends MouseListenerEvent {
/**
* The parent layer pane
*/
private JLayeredPane _parentLayeredPane;
/**
* The parent panel view
*/
private IView _parentPanelView;
/**
* Constructs a new instance of this class type
*/
public CardSelectionEvents() {
super(SupportedActions.LEFT);
}
@Override public void mousePressed(MouseEvent event) {
super.mousePressed(event);
if(getIsConsumed() && event.isConsumed()) {
return;
}
if(!isEnabled()) {
return;
}
// Get the parent of this card view, used as a reference to go back to whatever we were coming from
_parentLayeredPane = (JLayeredPane) CardView.this.getParent();
// Get the parent IView w.r.t this card view, this is used to make sure that we are not trying to collide with ourselves
_parentPanelView = CardView.this.getParentIView();
// Get the list of components that the parent owns
Component[] components =_parentLayeredPane.getComponents();
// Find the card that is being dragged and take all the siblings below it and populate them into the
// layered pane composed by the CardView.this reference
mainLabel : for(int i = components.length - 1; i >= 0; --i) {
if(components[i].equals(CardView.this)) {
// Get the siblings of cards within the components list (excluding CardView.this)
List cardViews = Arrays.asList(Arrays.copyOfRange(components, 0, i, CardView[].class));
// Reverse the list because layered panes associate objects closer to layer 0 as being closer to the screen.
Collections.reverse(cardViews);
// Fixes a bug where the layered pane is chopped from the waist down
CardView.this.layeredPane.setSize(layeredPane.getWidth(), layeredPane.getHeight() + (cardViews.size() * 15));
// For each sibling add it into the associated layered pane and position it correctly within
// the pane, accounting for the fact that CardView.this is the temporary 'root'
for(int j = 0; j < cardViews.size(); ++j) {
layeredPane.add(cardViews.get(j));
layeredPane.setLayer(cardViews.get(j), j);
// Account for the border offsets of -1 for left and bottom
cardViews.get(j).setBounds(new Rectangle(0, (15 * (j + 1)), cardViews.get(j).getPreferredSize().width, cardViews.get(j).getPreferredSize().height));
_parentLayeredPane.remove(cardViews.get(j));
}
// Position the card at the same place where the drag was attempted from, because when you
// add to the application it will position the component at the origin which is not desired
Point initialLocation = CardView.this.getLocation();
// Note: Add +2 to the width and height because of the initial border size offset
Rectangle rectangle = new Rectangle(_parentLayeredPane.getParent().getLocation().x + initialLocation.x, _parentLayeredPane.getParent().getLocation().y + initialLocation.y, layeredPane.getWidth(), layeredPane.getHeight());
CardView.this.setBounds(rectangle);
// Remove the card view reference from it's initial parent
_parentLayeredPane.remove(CardView.this);
// Get a reference to the game view and status view, and add the card into the proper
// z-order so that it appears underneath the status bar, but over everything else in the game
ViewFactory viewFactory = AbstractFactory.getFactory(ViewFactory.class);
GameView gameView = viewFactory.get(GameView.class);
StatusBarView statusBarView = viewFactory.get(StatusBarView.class);
gameView.add(CardView.this, gameView.getComponentZOrder(statusBarView) + 1);
gameView.repaint();
break mainLabel;
}
}
}
@Override public void mouseReleased(MouseEvent event) {
super.mouseReleased(event);
if(getIsConsumed() && event.isConsumed()) {
return;
}
if(!isEnabled()) {
return;
}
if(_parentLayeredPane == null) {
_parentPanelView = null;
return;
}
// If there is a valid collider, set that as the new parent
if(_collisionListener.getCollision() != null) {
AbstractPileView pileView = (AbstractPileView) _collisionListener.getCollision();
// Do not go further if what we have collided with is where we were already, that is not considered a valid collision.
// Note: Should this go inside of the drag listener?
if(!pileView.equals(_parentPanelView)) {
// Get the before movement type to know where the move is coming from
Optional layeredPane = Arrays.asList(pileView.getComponents()).stream().filter(z -> z.getClass() == JLayeredPane.class).findFirst();
if(layeredPane.isPresent()) {
AbstractFactory.getFactory(ControllerFactory.class).get(MovementRecorderController.class).recordMovement((IUndoable)_parentLayeredPane.getParent(), pileView);
_parentLayeredPane = (JLayeredPane) layeredPane.get();
}
else {
Tracelog.log(Level.SEVERE, true, "Could not find JLayeredPane within the CardView mouseReleased event...");
return;
}
}
}
// Get the offset that was set, and use this within our calculations
AbstractPileView parent = (AbstractPileView) _parentLayeredPane.getParent();
parent.addCard(CardView.this);
_parentLayeredPane = null;
_parentPanelView = null;
}
}
/**
* This mouse adapter handles events when the card is pressed with the mouse
*/
private MouseListenerEvent _mouseActionListener = new MouseListenerEvent(SupportedActions.LEFT) {
@Override public void mousePressed(MouseEvent event) {
super.mousePressed(event);
if(getIsConsumed() && event.isConsumed()) {
return;
}
if(!isEnabled()) {
return;
}
if(CardView.this.getParent().getComponents()[0].equals(CardView.this)) {
if(event.getClickCount() == 1) {
uncoverBackside(false);
}
else {
performCardAutoMovement();
}
}
}
};
/**
* The preferred width of this card
*/
public static final int CARD_WIDTH = 71;
/**
* The preferred height of this card
*/
public static final int CARD_HEIGHT = 96;
/**
* The controller associated to this card view
*/
private CardController _controller;
/**
* The draggable listener associated to this view
*/
private final DragListener _dragListener = new DragListener(SupportedActions.LEFT);
/**
* The collision listener associated to this view
*/
private final CollisionListener _collisionListener = new CollisionListener(this, SupportedActions.LEFT);
/**
* The layered pane that holds the potential list of cards that would be dragged along-side this card vuew
*/
protected final JLayeredPane layeredPane = new JLayeredPane();
/**
* Indicates if selections are enabled
*/
private boolean _highlightsEnabled;
/**
* The card selection events associated to this card view
*/
private final CardSelectionEvents _cardSelectionEvents = new CardSelectionEvents();
/**
* The card outline associated to this view
*/
private CardOutlineView _cardOutline;
/**
* Signal indicating that this view should synchronizr with the outline option
*/
public final static String EVENT_OUTLINE_SYNCHRONIZE = "EVENT_OUTLINE_SYNCHRONIZE";
private CardView() {
getViewProperties().setShouldAlwaysRedraw(true);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setPreferredSize(new Dimension(CARD_WIDTH, CARD_HEIGHT));
setOpaque(false);
setBackground(Color.BLACK);
add(layeredPane);
}
/**
* Constructs a new instance of this class type
*
* @param cardModel The cards model underlying this card view
*/
public CardView(CardModel cardModel) {
this();
cardModel.addListener(this);
_controller = new CardController(cardModel);
getViewProperties().setEntity(_controller);
// Create the card outline view and render it
_cardOutline = new CardOutlineView(this);
_cardOutline.render();
addMouseListener(_dragListener);
addMouseMotionListener(_dragListener);
addMouseListener(_collisionListener);
addMouseMotionListener(_collisionListener);
// Add the mouse listener responsible for handling single clicks and double clicks on this card.
// Note: This will sometimes not be called depending on if the outline is enabled or not, since the
// outline sits on top of this card. However, when the backside is being shown, this would indeed
// be called, however the double click will not be called since the single click will
// initiate the outline, thus the double click of the outline will be called
//
// Note: This mouse listener should be before any other mouse listener within this class
addMouseListener(_mouseActionListener);
addMouseListener(new MouseAdapter() {
@Override public void mousePressed(MouseEvent event) {
if(!SwingUtilities.isRightMouseButton(event)) {
AbstractFactory.getFactory(ViewFactory.class).get(TimerView.class).startGameTimer();
removeMouseListener(this);
}
}
});
// Register this view and it's underlying outline to perform auto moves
ViewHelper.registerForCardsAutocomplete(this);
ViewHelper.registerForCardsAutocomplete(_cardOutline);
// Register this view to handle the events raised by the card selection events
addMouseListener(_cardSelectionEvents);
addMouseMotionListener(_cardSelectionEvents);
// Listen in on events when we need to synchronize withthe online option
addSignal(EVENT_OUTLINE_SYNCHRONIZE, new ISignalReceiver() {
@Override public void signalReceived(EventArgs event) {
synchronizeWithOptions();
// When playing in draw three, make sure that cards that are not top-most are not enabled. This
// needs to be done after the synchronize.
OptionsPreferences optionsPreferences = new OptionsPreferences();
optionsPreferences.load();
if(optionsPreferences.drawOption == DrawOption.THREE && CardView.this.getParentIView().getClass() == TalonPileView.class) {
if(((JLayeredPane)getParent()).getPosition(CardView.this) > 0) {
_dragListener.setEnabled(false);
_collisionListener.setEnabled(false);
_cardSelectionEvents.setEnabled(false);
}
}
}
});
synchronizeWithOptions();
}
public static CardView createLightWeightCard(CardView cardView) {
CardView newCardView = new CardView();
newCardView.setEnabled(cardView.isEnabled());
cardView.getRenderableContent().forEach(z -> newCardView.addRenderableContent(z));
return newCardView;
}
/**
* Synchronizes this card view w.r.t the current outline options that are set within the game.
*/
private void synchronizeWithOptions() {
// Verify if the option for highlighting is enabled or not
OptionsPreferences optionsPreferences = new OptionsPreferences();
optionsPreferences.load();
_highlightsEnabled = optionsPreferences.outlineDragging;
// If the card has its backside shown or the outline option is enabled
// then do not allow dragging or collision to work as normal
boolean isAvailable = !_controller.getCard().getIsBackside() && !optionsPreferences.outlineDragging;
_dragListener.setEnabled(isAvailable);
_collisionListener.setEnabled(isAvailable);
// If the backside is not being shown, then add the event handler for card drag event
// Note: In the event that the options preferences calls for outline mode, the entire
// operation of performing a click-down, click-up, should be done by outline and
// not this card explicitely.
if(!_controller.getCard().getIsBackside()) {
if(!optionsPreferences.outlineDragging) {
remove(_cardOutline);
}
else {
add(_cardOutline);
}
}
// The selection events associated to this view should only be enabled if the
// backside is not shown and the outline mode is not enabled
_cardSelectionEvents.setEnabled(!_controller.getCard().getIsBackside() && !optionsPreferences.outlineDragging);
}
/**
* @return The card outline associated to this view
*/
public CardOutlineView getOutlineView() {
return _cardOutline;
}
/**
* Attempts to uncover the backside of this view
*
* @param forceBackside TRUE if the backside should be force uncovered, FALSE otherwise
*/
public void uncoverBackside(boolean forceBackside) {
if(_controller.getCard().getIsBackside()) {
_controller.getCard().setBackside(false);
_controller.getCard().refresh();
// Record the movement
if(!forceBackside) {
AbstractFactory.getFactory(ControllerFactory.class).get(MovementRecorderController.class).recordMovement((IUndoable)CardView.this.getParentIView(), null);
}
// Only allow this card view to have dragging and collision working `vanilla`
// style if the outline option is not selected
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(!preferences.outlineDragging) {
_dragListener.setEnabled(true);
_collisionListener.setEnabled(true);
_cardSelectionEvents.setEnabled(true);
}
if(preferences.outlineDragging) {
add(_cardOutline);
_cardOutline.setVisible(true);
}
repaint();
}
}
/**
* @return TRUE of the backside is showing, FALSE otherwise
*/
public boolean isBacksideShowing() {
return _controller.getCard().getIsBackside();
}
/**
* Performs an auto card movement, attempting to move this card to the foundation
*
* @return TRUE if the auto movement was successfull, FALSE otherwise
*/
public boolean performCardAutoMovement() {
return performCardAutoMovement(false);
}
/**
* Performs an auto card movement, attempting to move this card to the foundation
*
* @param forcefully If the movement is forcefully being done (when the user cheats to win the game)
*
* @return TRUE if the auto movement was successfull, FALSE otherwise
*/
public boolean performCardAutoMovement(boolean forcefully) {
if(!_controller.getCard().getIsBackside() && !(getParentIView() instanceof FoundationPileView)) {
// Make sure that we are not double clicking on an ACE. That doesn't make much sense here in this case
if(AbstractFactory.getFactory(ViewFactory.class).getAll(FoundationPileView.class).stream().anyMatch(z -> z.layeredPane.getComponentCount() == 1 && z.layeredPane.getComponents()[0] == CardView.this)) {
return false;
}
// Get the list of foundation views.
List foundationViews = AbstractFactory.getFactory(ViewFactory.class).getAll(FoundationPileView.class);
// Reverse the list, so that the card populating the left-most foundation view, this just looks a lot better
Collections.reverse(foundationViews);
// Go through the list of foundation views and see if there is a match
for(FoundationPileView foundationView : foundationViews) {
if(foundationView.isValidCollision(CardView.this)) {
if(!forcefully) {
// Record the fact that a movement occurred
AbstractFactory.getFactory(ControllerFactory.class).get(MovementRecorderController.class).recordMovement((AbstractPileView)CardView.this.getParentIView(), foundationView);
}
// Stop the current drag listener of this card from doing anything, so that things
// like drag will stop being processed
_dragListener.stopDragEvent();
// Add to the layered pane destination
foundationView.addCard(CardView.this);
// Repaint the components
layeredPane.repaint();
foundationView.repaint();
// Repaint the game view, this will fix a rendering bug where in outline mode, the status bar and
// the tableau view would not render properly. Look at bug #128.
AbstractFactory.getFactory(ViewFactory.class).get(GameView.class).repaint();
return true;
}
}
}
return false;
}
@Override public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
// Propagate the state change to the outline. Ensure that no circular reference could ever occur
if(_cardOutline != null && _cardOutline.isEnabled() != enabled) {
_cardOutline.setEnabled(enabled);
}
_dragListener.setEnabled(enabled);
_collisionListener.setEnabled(enabled);
}
@Override public void preprocessGraphics(IRenderable renderableData, Graphics context) {
super.preprocessGraphics(renderableData, context);
if(_highlightsEnabled && getIsHighlighted()) {
context.setXORMode(Color.WHITE);
}
}
@Override public void removeAll() {
super.removeAll();
_dragListener.setEnabled(false);
_collisionListener.setEnabled(false);
}
@Override public void render() {
super.render();
// Fixes a bug where clicking and then eventually dragging the cards causes a ton of issues with the size
renderProperties.x = 0;
renderProperties.y = 0;
renderProperties.width = this.getPreferredSize().width;
renderProperties.height = this.getPreferredSize().height;
if(_controller != null) {
_controller.refresh();
}
}
@Override public void update(EventArgs event) {
super.update(event);
addRenderableContent((IRenderable)event.getSource());
repaint();
}
@Override public void setBounds(int x, int y, int width, int height) {
// For now, this is here to fix a bug where the timer updating on the status bar will
// cause a revalidation of the gridbag that the game view has, causing the card that is dragging
// to be reset to the `origin`.
//System.out.printf("(%d,%d,%d%d)\n", x, y, width, height);
if(x == 13 && y == 6) {
return;
}
super.setBounds(x, y, width, height);
}
@Override public String toString() {
// Hold onto the layer position
int layer = JLayeredPane.getLayer(this);
// Attempt to get the position within the layer.
int positionWithinLayer = -1;
if(this.getParent() instanceof JLayeredPane) {
JLayeredPane parentLayeredPane = (JLayeredPane) this.getParent();
List components = Arrays.asList(parentLayeredPane.getComponentsInLayer(layer));
positionWithinLayer = components.indexOf(CardView.this);
}
return String.format("%s%s%s\t[%d][%d]",
isVisible() ? "[V]" : "[H]",
isEnabled() ? "[E]" : "[D]",
getViewProperties().getEntity(CardController.class).getCard(),
layer,
positionWithinLayer
);
}
@Override public boolean isValidCollision(Component source) {
IView view = (IView)source;
// A card is coming into this card, and we are on the foundation view
if(getParent().getParent() instanceof FoundationPileView) {
CardController thisCardViewController = view.getViewProperties().getEntity(CardController.class);
return _controller.isValidFoundationMove(thisCardViewController.getCard());
}
// The card is coming onto this card which is on the pile view (should be, there are only two options for this game)
else {
CardController cardViewController = this.getViewProperties().getEntity(CardController.class);
return cardViewController.getCard().isCardBeforeAndOppositeSuite(view.getViewProperties().getEntity(CardController.class).getCard());
}
}
@Override public void onCollisionStart(Component source) {
// Set the opaque to true to color in the card. It is originally false so
// that the corners with are transparent will not be shown, since setting this
// to true forces the card dimensions to show something.
this.setOpaque(true);
}
@Override public void onCollisionStop(Component source) {
// Set the opaque back to false which is the default for all cards
this.setOpaque(false);
}
}
================================================
FILE: src/game/views/DeckSelectionDialogView.java
================================================
package game.views;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultButtonModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import framework.core.entity.DataEntity;
import framework.core.mvc.view.DialogView;
import framework.core.system.Application;
import framework.utils.globalisation.Localization;
import game.config.OptionsPreferences;
import generated.DataLookup;
import generated.DataLookup.BACKSIDES;
/**
* The deck selection dialog view shows the list of deck images that the user can choose from while playing the game
*
* @author Daniel Ricci {@literal }
*
*/
public final class DeckSelectionDialogView extends DialogView {
/**
* This class represents a model that is used within one of the deck buttons
*
* @author Daniel Ricci {@literal }
*/
private class FixedStateButtonModel extends DefaultButtonModel {
@Override public boolean isPressed() {
return false;
}
@Override public boolean isRollover() {
return false;
}
@Override public boolean isArmed() {
return false;
}
@Override public boolean isSelected() {
return false;
}
}
/**
* The number of rows in the card list
*/
private final int _cardRows = 2;
/**
* The number of columns in the card list
*/
private final int _cardColumns = 6;
/**
* The button width
*/
private final int _buttonWidth = 45;
/**
* The button height
*/
private final int _buttonHeight = 74;
/**
* The image width of the button
*/
private final int _buttonImageWidth = 39;
/**
* The image height of the button
*/
private final int _buttonImageHeight = 68;
/**
* The OK button
*/
private final JButton _okButton = new JButton("OK");
/**
* The Cancel button
*/
private final JButton _cancelButton = new JButton("Cancel");
/**
* The current active button
*/
private JButton _activeButton;
/**
* The list of buttons that hold a deck option
*/
private final List _deckButtons = new ArrayList();
/**
* Constructs a new instance of this class type
*/
public DeckSelectionDialogView() {
super(Application.instance, Localization.instance().getLocalizedString("Select Card Back"));
getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
setAutomaticDialogCentering(true);
setModal(true);
setAlwaysOnTop(true);
setResizable(false);
}
@Override protected void enterActionPerformed(ActionEvent event) {
_okButton.doClick();
}
@Override public void render() {
// Get a reference to all the backside data values
DataLookup.BACKSIDES[] backsides = DataLookup.BACKSIDES.values();
// Set the initial constraints for the grid bag layout
GridBagConstraints drawPanelConstraints = new GridBagConstraints();
drawPanelConstraints.weighty = 1;
drawPanelConstraints.weightx = 1;
drawPanelConstraints.insets = new Insets(5, 5, 5, 5);
// The panel that holds the list of cards
JPanel cardPanel = new JPanel(new GridBagLayout());
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
// Go through card rows and card columns, and populate each index with a JButton
// containing one of the card images
for(int row = 0, index = 0; row < _cardRows; ++row) {
drawPanelConstraints.gridy = row;
for(int column = 0; column < _cardColumns; ++column, ++index) {
// Create the backside entity and assign it to the button
//BacksideCardEntity entity = new BacksideCardEntity();
DataEntity entity = new DataEntity(backsides[index].identifier);
// Create the button and set the size we want it to be
JButton button = new JButton(new ImageIcon(entity.getRenderableContent().getScaledInstance(_buttonImageWidth, _buttonImageHeight, java.awt.Image.SCALE_SMOOTH)));
// Set the border of the button based on what was currently set in the preferences, and make sure to set it as active within this dialog
if(preferences.deck == backsides[index]) {
button.setBorder(BorderFactory.createLineBorder(Color.BLUE, 2));
_activeButton = button;
}
button.setModel(new FixedStateButtonModel());
button.setContentAreaFilled(false);
button.setFocusPainted(false);
button.setPreferredSize(new Dimension(_buttonWidth, _buttonHeight));
button.putClientProperty(button, backsides[index]);
// Add a listener event for when the deck image is selected
button.addMouseListener(new MouseAdapter() {
@Override public void mousePressed(MouseEvent event) {
_deckButtons.forEach(z -> z.setBorder(BorderFactory.createEmptyBorder()));
button.setBorder(BorderFactory.createLineBorder(Color.BLUE, 2));
_activeButton = button;
// If more than one click count was registered, then do what the OK button would do.
// Note that I don't call doClick because I don't want the OK button to look like it was clicked
if(event.getClickCount() > 1) {
ok();
}
}
});
// Add a reference to the list of buttons
_deckButtons.add(button);
// Specify the proper column constraint
drawPanelConstraints.gridx = column;
// Add the button to the panel
cardPanel.add(button, drawPanelConstraints);
}
}
// The OK button action event
_okButton.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent event) {
ok();
}
});
// The Cancel button action event
_cancelButton.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent event) {
cancel();
}
});
// Add the okay and cancel buttons
JPanel actionsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
actionsPanel.add(_okButton);
actionsPanel.add(_cancelButton);
// Add the main housing panels to this dialog
add(cardPanel);
add(actionsPanel);
// Pack the UI to fit
pack();
// Render the UI
super.render();
}
private void cancel() {
setDialogResult(JOptionPane.CANCEL_OPTION);
setVisible(false);
}
private void ok() {
BACKSIDES back = (BACKSIDES) _activeButton.getClientProperty(_activeButton);
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(preferences.deck == back) {
cancel();
}
else {
preferences.deck = back;
preferences.save();
setDialogResult(JOptionPane.OK_OPTION);
setVisible(false);
}
}
}
================================================
FILE: src/game/views/FoundationPileView.java
================================================
package game.views;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.SwingUtilities;
import framework.api.IView;
import framework.communication.internal.signal.arguments.EventArgs;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.graphics.IRenderable;
import framework.core.physics.ICollidable;
import game.controllers.CardController;
import game.entities.FoundationCardEntity;
import game.views.helpers.ViewHelper;
/**
* This view represents the foundation pile view
*
* @author Daniel Ricci {@literal }
*/
public final class FoundationPileView extends AbstractPileView implements ICollidable {
/**
* Creates a new instance of this class type
*/
public FoundationPileView() {
// The background the the opaqueness of this view
// must be set this way to achieve the proper xor effect
this.setBackground(Color.BLACK);
this.setOpaque(true);
addRenderableContent(new FoundationCardEntity());
ViewHelper.registerForCardsAutocomplete(this);
addMouseListener(new MouseAdapter() {
@Override public void mousePressed(MouseEvent event) {
if(!SwingUtilities.isRightMouseButton(event)) {
AbstractFactory.getFactory(ViewFactory.class).get(TimerView.class).startGameTimer();
removeMouseListener(this);
}
}
});
}
@Override public void preprocessGraphics(IRenderable renderableData, Graphics context) {
super.preprocessGraphics(renderableData, context);
if(getIsHighlighted() && layeredPane.getComponentCount() == 0) {
context.setXORMode(Color.WHITE);
}
}
@Override public Dimension getPreferredSize() {
return new Dimension(CardView.CARD_WIDTH, CardView.CARD_HEIGHT);
}
@Override public void update(EventArgs event) {
super.update(event);
repaint();
}
@Override public boolean isValidCollision(Component source) {
if (layeredPane.getComponentCount() == 0) {
return ((IView) source).getViewProperties().getEntity(CardController.class).getCard().getCardEntity().isAceCard();
} else {
CardView thisCardView = (CardView) layeredPane.getComponent(0);
return thisCardView.isValidCollision(source);
}
}
@Override public void addCard(CardView cardView) {
super.addCard(cardView);
GameView.scanGameForWin();
}
@Override protected Point getCardOffset(CardView cardView) {
return new Point(0, 0);
}
@Override public void onCollisionStart(Component source) {
CardView cardView = this.getLastCard();
if(cardView != null) {
cardView.onCollisionStart(source);
}
}
@Override public void onCollisionStop(Component source) {
CardView cardView = this.getLastCard();
if(cardView != null) {
cardView.onCollisionStop(source);
}
}
}
================================================
FILE: src/game/views/GameView.java
================================================
package game.views;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractButton;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ControllerFactory;
import framework.core.factories.ViewFactory;
import framework.core.mvc.view.PanelView;
import framework.core.navigation.MenuBuilder;
import framework.core.system.Application;
import framework.utils.globalisation.Localization;
import game.config.OptionsPreferences;
import game.controllers.MovementRecorderController;
import game.menu.NewGameMenuItem;
import game.models.CardModel;
import game.views.helpers.ViewHelper;
import game.views.helpers.WinAnimationHelper;
import resources.LocalizationStrings;
/**
* The game view wraps a draggable layout around the entire game
*
* @author {@literal Daniel Ricci {@literal }}
*
*/
public final class GameView extends PanelView {
/**
* Creates a new instance of this class type
*/
public GameView() {
this.setLayout(new GridBagLayout());
this.setBackground(new Color(0, 128, 0));
// Configure constraint initial values
final int _rowSize = 2;
final int _columnSize = 7;
GridBagConstraints gameConstraints = new GridBagConstraints();
gameConstraints.weightx = 1.0;
gameConstraints.anchor = GridBagConstraints.NORTH;
ViewFactory viewFactory = AbstractFactory.getFactory(ViewFactory.class);
// Create the total list of cards
List cards = CardModel.newInstances();
// Create the globally available movement controller
AbstractFactory.getFactory(ControllerFactory.class).add(new MovementRecorderController(), true);
for(int row = _rowSize - 1; row >= 0; --row) {
gameConstraints.gridy = row;
if(gameConstraints.gridy == 1) {
gameConstraints.weighty = 1;
gameConstraints.insets = new Insets(10, 0, 0, 0);
gameConstraints.fill = GridBagConstraints.VERTICAL;
}
else {
gameConstraints.weighty = 0;
gameConstraints.insets = new Insets(10, 0, 0, 0);
}
for(int col = _columnSize - 1; col >= 0; --col) {
gameConstraints.gridx = col;
if(gameConstraints.gridy == 0) {
switch(gameConstraints.gridx) {
case 0: {
// Create the stock view
StockView stockView = viewFactory.add(new StockView(), true);
GridBagConstraints constraints = (GridBagConstraints)gameConstraints.clone();
constraints.insets = new Insets(gameConstraints.insets.top, 4, 0, 0);
this.add(stockView, constraints);
break;
}
case 1: {
// Create the talon view
TalonPileView talonView = viewFactory.add(new TalonPileView(cards), true);
GridBagConstraints constraints = (GridBagConstraints)gameConstraints.clone();
constraints.insets = new Insets(gameConstraints.insets.top, 0, -5, -30);
constraints.ipadx = 30;
constraints.ipady = 5;
this.add(talonView, constraints, 0);
break;
}
case 3:
case 4:
case 5:
case 6:
// Create the foundation view
FoundationPileView foundationView = viewFactory.add(new FoundationPileView());
GridBagConstraints constraints = (GridBagConstraints)gameConstraints.clone();
constraints.fill = GridBagConstraints.NONE;
this.add(foundationView, constraints);
break;
}
}
else {
List subList = cards.subList(0, gameConstraints.gridx + 1);
TableauPileView view = viewFactory.add(new TableauPileView(new ArrayList(subList)));
subList.clear();
this.add(view, gameConstraints);
}
}
}
addStatusBarView();
addMouseListener(new MouseAdapter() {
@Override public void mousePressed(MouseEvent event) {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(preferences.timedGame) {
if(!SwingUtilities.isRightMouseButton(event)) {
AbstractFactory.getFactory(ViewFactory.class).get(TimerView.class).startGameTimer();
removeMouseListener(this);
}
}
}
});
ViewHelper.registerForCardsAutocomplete(this);
}
/**
* Adds the status bar view to this view
*/
private void addStatusBarView() {
StatusBarView statusBarView = AbstractFactory.getFactory(ViewFactory.class).add(new StatusBarView(), true);
GridBagConstraints barConstraints = new GridBagConstraints();
barConstraints.anchor = GridBagConstraints.SOUTH;
barConstraints.gridx = 0;
barConstraints.gridy = 1;
barConstraints.fill = GridBagConstraints.HORIZONTAL;
barConstraints.weightx = 1.0;
barConstraints.weighty = 1.0;
barConstraints.gridwidth = 7;
barConstraints.insets = new Insets(0, -2, 0, -2);
add(statusBarView, barConstraints, 0);
}
/**
* Gets the current card component used on this view. This is the card
* that would exist when performing a drag operation
*
* @return The currently dragged card of this view
*/
public CardView getCardComponent() {
for(Component comp : getComponents()) {
if(comp instanceof CardView) {
return (CardView) comp;
}
}
return null;
}
/**
* Scans the board for a win condition
*/
public static void scanGameForWin() {
boolean isWinner = true;
for(FoundationPileView foundationView : AbstractFactory.getFactory(ViewFactory.class).getAll(FoundationPileView.class)) {
if(foundationView.layeredPane.getComponentCount() != 13) {
isWinner = false;
break;
}
}
if(isWinner) {
processWin();
}
}
/**
* Forces the game to win
*/
public static void forceGameWin() {
List cards = AbstractFactory.getFactory(ViewFactory.class).getAll(CardView.class);
cards.stream().forEach(z -> z.uncoverBackside(true));
cards.stream().forEach(z -> z.setVisible(true));
while(cards.size() > 0) {
boolean keepGoing = false;
for(int i = 0; i < cards.size(); ++i) {
if(cards.get(i).performCardAutoMovement(true)) {
cards.get(i).setEnabled(false);
cards.remove(i);
keepGoing = true;
break;
}
}
if(!keepGoing) {
break;
}
}
}
/**
* Process the events that will occur after a win has been detected
*/
private static void processWin() {
// Stop the game timer
TimerView gameTimerView = AbstractFactory.getFactory(ViewFactory.class).get(TimerView.class);
gameTimerView.stop();
// Update the score with the bonus
long bonus = AbstractFactory.getFactory(ViewFactory.class).get(ScoreView.class).updateScoreBonus(gameTimerView.getTime());
// Show the updated text on the status bar
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).setMenuDescription(String.format(Localization.instance().getLocalizedString(LocalizationStrings.GAME_WON_STATUS_BAR), bonus));
// Perform the animation on all the cards
WinAnimationHelper.processCards();
}
/**
* Shows the game over dialog, prompting the user to choose what they would like to do
*/
public static void showGameOverDialog() {
// Show the dialog indicating that game has won
if(JOptionPane.showConfirmDialog(Application.instance, Localization.instance().getLocalizedString(LocalizationStrings.GAME_OVER), Localization.instance().getLocalizedString(LocalizationStrings.GAME_OVER_HEADER), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) {
MenuBuilder.search(Application.instance.getJMenuBar(), NewGameMenuItem.class).getComponent(AbstractButton.class).doClick();
}
else {
// Clear the description and other status bar texts
AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).clearMenuDescription();
GameView gameView = AbstractFactory.getFactory(ViewFactory.class).get(GameView.class);
// Get the tree and synchronize against it. Exceptions will be thrown during the removal process if we dont
synchronized(gameView.getTreeLock()) {
Component[] components = AbstractFactory.getFactory(ViewFactory.class).get(GameView.class).getComponents();
for(int i = 0; i < components.length; ++i) {
if(!(components[i] instanceof StatusBarView)) {
gameView.remove(components[i]);
}
}
Application.instance.repaint();
}
}
}
}
================================================
FILE: src/game/views/IUndoable.java
================================================
package game.views;
import framework.api.IView;
/**
* Defines the functionality required to perform an undo operation on a particular component
*
* @author Daniel Ricci {@literal }
*/
public interface IUndoable extends IView {
/**
* Performs an undo based on the last backup that was performed on this component
*/
public void undoLastAction();
/**
* Performs a logical backup with respect to the state and business logic of this component
*/
public void performBackup();
/**
* Clears the backup associated to this component
*/
public void clearBackup();
}
================================================
FILE: src/game/views/OptionsDialogView.java
================================================
package game.views;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import framework.core.mvc.view.DialogView;
import framework.core.system.Application;
import framework.utils.globalisation.Localization;
import game.config.OptionsPreferences;
import game.config.OptionsPreferences.DrawOption;
import game.config.OptionsPreferences.ScoringOption;
/**
* The options view shows the settings that change the way the game is played
*
* @author Daniel Ricci {@literal }
*
*/
public final class OptionsDialogView extends DialogView {
/**
* Indicates if the options that were changed (if any) require the game to reset to take effect
*/
public boolean refreshGameRequired;
/**
* Indicates if the status bar option has changed since it's last saved value
*/
public boolean statusBarChanged;
/**
* Indicates if the outline dragging option has changed since it's last saved value
*/
public boolean outlineDraggingChanged;
/**
* Indicates if the cumulative score option has changed since it's last saved value
*/
public boolean cumulativeScoreChanged;
// The OK button
private JButton okButton = new JButton("OK");
/**
* Constructs a new instance of this class type
*/
public OptionsDialogView() {
super(Application.instance, Localization.instance().getLocalizedString("Options"));
// Properties of this view
getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
setAutomaticDialogCentering(true);
setModal(true);
setAlwaysOnTop(true);
setResizable(false);
// The main panel that houses this view
JPanel mainPanel = new JPanel();
// Use a grid to lay out the contents
mainPanel.setLayout(new GridLayout(2, 2));
// Load the option for this view
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
// Draw radio buttons
GridLayout drawPanelGridLayout = new GridLayout(2, 1);
drawPanelGridLayout.setVgap(-10);
JPanel drawPanel = new JPanel();
drawPanel.setLayout(drawPanelGridLayout);
drawPanel.setBorder(BorderFactory.createTitledBorder("Draw"));
JRadioButton drawOneRadioButton = new JRadioButton("Draw One", preferences.drawOption == DrawOption.ONE);
drawOneRadioButton.putClientProperty(drawOneRadioButton, DrawOption.ONE);
JRadioButton drawThreeRadioButton = new JRadioButton("Draw Three", preferences.drawOption == DrawOption.THREE);
drawThreeRadioButton.putClientProperty(drawThreeRadioButton, DrawOption.THREE);
// Button group for the draw radio buttons
ButtonGroup drawPanelGroup = new ButtonGroup();
drawPanelGroup.add(drawOneRadioButton);
drawPanelGroup.add(drawThreeRadioButton);
drawPanel.add(drawOneRadioButton);
drawPanel.add(drawThreeRadioButton);
// Scoring Panel UI
GridLayout scoringPanelGridLayout = new GridLayout(3, 1);
scoringPanelGridLayout.setVgap(-5);
JPanel scoringPanel = new JPanel();
scoringPanel.setLayout(scoringPanelGridLayout);
scoringPanel.setBorder(BorderFactory.createTitledBorder("Scoring"));
// Scoring radio buttons
JRadioButton standardRadioButton = new JRadioButton("Standard", preferences.scoringOption == ScoringOption.STANDARD);
standardRadioButton.putClientProperty(standardRadioButton, ScoringOption.STANDARD);
JRadioButton vegasRadioButton = new JRadioButton("Vegas", preferences.scoringOption == ScoringOption.VEGAS);
vegasRadioButton.putClientProperty(vegasRadioButton, ScoringOption.VEGAS);
JRadioButton noneRadioButton = new JRadioButton("None", preferences.scoringOption == ScoringOption.NONE);
noneRadioButton.putClientProperty(noneRadioButton, ScoringOption.NONE);
// Button group for the scoring radio buttons
ButtonGroup scoringPanelGroup = new ButtonGroup();
scoringPanelGroup.add(standardRadioButton);
scoringPanelGroup.add(vegasRadioButton);
scoringPanelGroup.add(noneRadioButton);
scoringPanel.add(standardRadioButton);
scoringPanel.add(vegasRadioButton);
scoringPanel.add(noneRadioButton);
// Left side options
GridLayout leftSideGridLayout = new GridLayout(3, 1);
JPanel barOptionsPanelLeft = new JPanel();
barOptionsPanelLeft.setLayout(leftSideGridLayout);
// Mutually exclusive checkbox settings
JCheckBox timedGameCheckBox = new JCheckBox("Timed Game", preferences.timedGame);
barOptionsPanelLeft.add(timedGameCheckBox);
JCheckBox statusBarCheckBox = new JCheckBox("Status Bar", preferences.statusBar);
barOptionsPanelLeft.add(statusBarCheckBox);
JCheckBox outlineDraggingCheckbox = new JCheckBox("Outline Dragging", preferences.outlineDragging);
barOptionsPanelLeft.add(outlineDraggingCheckbox);
// Right side options
JPanel barOptionsPanelRight = new JPanel();
barOptionsPanelRight.setLayout(new BoxLayout(barOptionsPanelRight, BoxLayout.Y_AXIS));
JCheckBox cumulativeScoreCheckBox = new JCheckBox("Cumulative Score", preferences.cumulativeScore);
cumulativeScoreCheckBox.setEnabled(vegasRadioButton.isSelected());
// Add an item listener to the vegas radio button to alter the state of the cumulative score
vegasRadioButton.addItemListener(new ItemListener() {
@Override public void itemStateChanged(ItemEvent event) {
cumulativeScoreCheckBox.setEnabled(event.getStateChange() == ItemEvent.SELECTED);
}
});
barOptionsPanelRight.add(cumulativeScoreCheckBox);
okButton.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent event) {
// Draw panel result
JRadioButton drawPanelSelection = drawOneRadioButton.isSelected() ? drawOneRadioButton : drawThreeRadioButton;
DrawOption drawOptionNew = (DrawOption)drawPanelSelection.getClientProperty(drawPanelSelection);
refreshGameRequired = preferences.drawOption != drawOptionNew;
preferences.drawOption = drawOptionNew;
// Scoring result
JRadioButton scoringPanelSelection = null;
if(standardRadioButton.isSelected()) {
scoringPanelSelection = standardRadioButton;
}
else if(vegasRadioButton.isSelected()) {
scoringPanelSelection = vegasRadioButton;
}
else {
scoringPanelSelection = noneRadioButton;
}
ScoringOption scoringOption = (ScoringOption)scoringPanelSelection.getClientProperty(scoringPanelSelection);
refreshGameRequired |= scoringOption != preferences.scoringOption;
preferences.scoringOption = scoringOption;
// Timed game
boolean timedGame = timedGameCheckBox.isSelected();
refreshGameRequired |= timedGame != preferences.timedGame;
preferences.timedGame = timedGame;
// Status bar
boolean statusBar = statusBarCheckBox.isSelected();
statusBarChanged = preferences.statusBar != statusBar;
preferences.statusBar = statusBar;
// Outline dragging
boolean outlineDragging = outlineDraggingCheckbox.isSelected();
outlineDraggingChanged = preferences.outlineDragging != outlineDragging;
preferences.outlineDragging = outlineDragging;
// Cumulative Score
boolean cumulativeScore = cumulativeScoreCheckBox.isSelected();
cumulativeScoreChanged = preferences.cumulativeScore != cumulativeScore;
preferences.cumulativeScore = cumulativeScore;
// Save the contents of the preferences and then close this dialog
preferences.save();
setDialogResult(JOptionPane.OK_OPTION);
setVisible(false);
}
});
// The Cancel button
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent event) {
setDialogResult(JOptionPane.CANCEL_OPTION);
setVisible(false);
}
});
// Add the OK button and the Cancel button
JPanel actionsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
actionsPanel.add(okButton);
actionsPanel.add(cancelButton);
// Add the sub-panels to the main panel
mainPanel.add(drawPanel);
mainPanel.add(scoringPanel);
mainPanel.add(barOptionsPanelLeft);
mainPanel.add(barOptionsPanelRight);
// Add the main panel and the action panel to the main view;
add(mainPanel);
add(actionsPanel);
}
@Override protected void enterActionPerformed(ActionEvent event) {
super.enterActionPerformed(event);
this.okButton.doClick();
}
}
================================================
FILE: src/game/views/ScoreView.java
================================================
package game.views;
import java.awt.Color;
import java.awt.FlowLayout;
import javax.swing.JLabel;
import framework.communication.internal.signal.arguments.EventArgs;
import framework.core.mvc.view.PanelView;
import framework.utils.globalisation.Localization;
import game.config.OptionsPreferences;
import game.config.OptionsPreferences.DrawOption;
import game.config.OptionsPreferences.ScoringOption;
import game.models.MovementModel;
import game.models.MovementModel.MovementType;
import resources.LocalizationStrings;
/**
* This view shows the game score
*
* @author Daniel Ricci {@literal }
*/
public class ScoreView extends PanelView {
/**
* The label that represents the title of this class
*/
protected final JLabel scoreTitle = new JLabel(Localization.instance().getLocalizedString(LocalizationStrings.SCORE_TITLE));
/**
* The label that represents the visual representation of this class
*/
protected final JLabel scoreValue = new JLabel();
/**
* The current score
*/
protected static long SCORE_CURRENT;
/**
* The previous score
*/
@SuppressWarnings("unused")
private static long SCORE_BEFORE = SCORE_CURRENT;
/**
* Constructs a new instance of this class type
*/
public ScoreView() {
this.setBackground(Color.WHITE);
// Set the VGap so that the score renders appropriately
FlowLayout layout = (FlowLayout) this.getLayout();
layout.setVgap(0);
add(scoreTitle);
add(scoreValue);
}
/**
* Adds the specified offset to the current score.
*
* @param offsetToScore The score to offset the current score with, a +- value of something defined on your end
*/
protected void addToScore(long offsetToScore) {
SCORE_CURRENT = Math.max(0, SCORE_CURRENT + offsetToScore);
scoreValue.setText(toString());
}
/**
* Adds the specified offset to the current score. This method will perform a backup of what the
* score previously was. How this method differs from the `addToScore` method is that it
* is friendly for performing an undo.
*
* The reason to not call this method over the other is in the case where the score being perform
* could not ever be undone, such as when 10 seconds has elapsed and the score loses -2, you can never
* get that back so dont backup the score there
*
* @param offsetToScore The score to offset the current score with, a +- value of something defined on your end
*/
private void addToScoreAndBackup(long offsetToScore) {
SCORE_BEFORE = SCORE_CURRENT;
addToScore(offsetToScore);
}
/**
* Updates the score based on the bonus logic
*
* @param seconds The number of seconds that has elapsed in the game
*
* @return The bonus that will be used
*/
public long updateScoreBonus(long seconds) {
long bonus = 0;
if(seconds > 30) {
bonus = 700000 / seconds;
addToScore(bonus);
}
return bonus;
}
/**
* Updates the score based on the specified deck plays.
*
* @param deckPlays The number of decks played
*/
public void updateScoreDeckFinished(int deckPlays) {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(preferences.drawOption == DrawOption.THREE && preferences.scoringOption == ScoringOption.STANDARD) {
if(deckPlays > 3) {
addToScoreAndBackup(-20);
}
}
else {
addToScoreAndBackup(-100);
}
}
/**
* Updates the score based on a timer interval tick of the game
*/
public void updateScoreTimerTick() {
addToScore(-2);
}
/**
* Updates the score based on a cards' backside being revealed
*/
public void updateScoreCardTurnOver() {
addToScore(5);
}
/**
* Updates the score based on an operation
*
* @param from where the operation started from
* @param to where the operation ended at
* @param isUndo If the operation being done is an undo operation
*
*/
protected void updateScore(MovementType from, MovementType to, boolean isUndo) {
long scoreFromMovement = getMovementScore(from, to, isUndo);
addToScoreAndBackup(scoreFromMovement + (isUndo ? - 2 : 0));
}
private long getMovementScore(MovementType from, MovementType to, boolean isUndo) {
long score = 0;
if(from == MovementType.TALON && to == MovementType.TABLEAU) {
score = 5;
}
else if(from == MovementType.TALON && to == MovementType.FOUNDATION) {
score = 10;
}
else if (from == MovementType.TABLEAU && to == MovementType.FOUNDATION) {
score = 10;
}
else if(from == MovementType.FOUNDATION && to == MovementType.TABLEAU) {
score = -15;
}
// If the score is being undone then flip the sign of the score
if(isUndo) {
score *= -1;
}
return score;
}
@Override public void render() {
super.render();
addToScore(0);
}
@Override public void destructor() {
super.destructor();
SCORE_CURRENT = 0;
SCORE_BEFORE = 0;
}
@Override public String toString() {
return String.valueOf(SCORE_CURRENT);
}
@Override public final void update(EventArgs event) {
if(event.getSource() instanceof MovementModel) {
MovementModel movement = (MovementModel) event.getSource();
updateScore(movement.getFrom(), movement.getTo(), movement.getIsUndo());
}
}
}
================================================
FILE: src/game/views/StatusBarView.java
================================================
package game.views;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ControllerFactory;
import framework.core.factories.ViewFactory;
import framework.core.mvc.view.PanelView;
import game.config.OptionsPreferences;
import game.config.OptionsPreferences.ScoringOption;
import game.controllers.MovementRecorderController;
public final class StatusBarView extends PanelView {
/**
* The game timer view
*/
private final TimerView _gameTimerView = AbstractFactory.getFactory(ViewFactory.class).add(new TimerView(), true);
/**
* The game score view
*/
private ScoreView _scoreView = null;
/**
* The menu description label
*/
private final JLabel _menuDescription = new JLabel();
/**
* Constructs a new instance of this class type
*/
public StatusBarView() {
this.setBackground(Color.WHITE);
this.setLayout(new BorderLayout());
this.setPreferredSize(new Dimension(getPreferredSize().width, 17));
this.setFocusable(false);
setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY));
// Add a dummy mouse listener to avoid the click from hitting the green background
addMouseListener(new MouseAdapter() {
@Override public void mousePressed(MouseEvent event) {
}
});
// Menu Descrition
_menuDescription.setBorder(new EmptyBorder(0, 5, 0, 0));
add(_menuDescription, BorderLayout.WEST);
// Game Score + Game Timer
JPanel rightSidePanel = new JPanel(new BorderLayout());
rightSidePanel.setBorder(BorderFactory.createEmptyBorder());
// The scoring option should only be shown in Standard and Vegas scoring modes
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
// Create the score view based on the currently set scoring standard
_scoreView = AbstractFactory.getFactory(ViewFactory.class).add(preferences.scoringOption == ScoringOption.VEGAS ? new VegasScoreView() : new ScoreView(), true);
_scoreView.setBorder(null);
_scoreView.render();
// register the score view to recieve events from the movement controller
AbstractFactory.getFactory(ControllerFactory.class).get(MovementRecorderController.class).addSignalListener(_scoreView);
if(preferences.scoringOption != ScoringOption.NONE) {
rightSidePanel.add(_scoreView,BorderLayout.WEST);
}
rightSidePanel.add(_gameTimerView, BorderLayout.EAST);
add(rightSidePanel, BorderLayout.EAST);
// Synchronize w.r.t the currently set options
synchronizeWithOptions();
}
/**
* Sets the menu description
* @param text The description to set
*/
public void setMenuDescription(String text) {
_menuDescription.setText(text);
}
/**
* Convenience method to clear the text
*/
public void clearMenuDescription() {
setMenuDescription("");
}
/**
* Synchronizes the options results w.r.t the status bar and it's related content
*/
public void synchronizeWithOptions() {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
this.setVisible(preferences.statusBar);
_gameTimerView.setVisible(preferences.timedGame);
_scoreView.setVisible(preferences.scoringOption != ScoringOption.NONE);
}
@Override public void render() {
synchronizeWithOptions();
}
}
================================================
FILE: src/game/views/StockView.java
================================================
package game.views;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SwingUtilities;
import framework.communication.internal.signal.ISignalReceiver;
import framework.communication.internal.signal.arguments.EventArgs;
import framework.communication.internal.signal.arguments.ViewEventArgs;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.graphics.IRenderable;
import framework.core.mvc.view.PanelView;
import framework.utils.MouseListenerEvent;
import framework.utils.MouseListenerEvent.SupportedActions;
import game.entities.BacksideCardEntity;
import game.entities.StockCardEntity;
import game.views.TalonPileView.TalonCardState;
import game.views.helpers.DeckAnimationHelper;
import game.views.helpers.ViewHelper;
import generated.DataLookup;
/**
* This view handles the functionality related to the Stock. This view will show the
* collated cards view based on the deck face, and when you click on this view it will
* cause the Talon to update the card list that it holds.
*
* @author Daniel Ricci {@literal }
*/
public final class StockView extends PanelView implements IUndoable {
/**
* The list of card entities that make up this view
*/
private final List _stockCardEntities = new ArrayList() {{
add(new StockCardEntity());
add(new StockCardEntity());
add(new StockCardEntity());
}};
/**
* A reference to the Talon view. This is stored so that we don't need to query the view factory to get the Talon
* which is costly given where it is being used
*/
private final TalonPileView _talonView = AbstractFactory.getFactory(ViewFactory.class).get(TalonPileView.class);
/**
* Constructs a new instance of this class type
*/
public StockView() {
setOpaque(false);
ViewHelper.registerForCardsAutocomplete(this);
addMouseListener(new MouseAdapter() {
@Override public void mousePressed(MouseEvent event) {
if(!SwingUtilities.isRightMouseButton(event)) {
AbstractFactory.getFactory(ViewFactory.class).get(TimerView.class).startGameTimer();
removeMouseListener(this);
}
}
});
addMouseListener(new MouseListenerEvent(SupportedActions.LEFT) {
@Override public void mousePressed(MouseEvent event) {
super.mousePressed(event);
if(getIsConsumed()) {
return;
}
_talonView.cycleNextHand();
TalonCardState talonState = _talonView.getState();
if(talonState == TalonCardState.DECK_PLAYED) {
if(_talonView.isTalonEnded()) {
_stockCardEntities.get(0).enableTalonEnd();
}
else {
_stockCardEntities.get(0).enableTalonRecycled();
}
DeckAnimationHelper.getInstance().setScene(_stockCardEntities);
}
else {
StockCardEntity stockCardEntity = _stockCardEntities.get(0);
Boolean needsUpdate = stockCardEntity.getActiveDataIdentifier() == DataLookup.MISC.TALON_RESTART.identifier || stockCardEntity.getActiveDataIdentifier() == DataLookup.MISC.TALON_END.identifier;
_stockCardEntities.remove(0);
_stockCardEntities.add(0, new StockCardEntity());
if(needsUpdate) {
DeckAnimationHelper.getInstance().setScene(_stockCardEntities);
}
if(!SwingUtilities.isRightMouseButton(event)) {
AbstractFactory.getFactory(ViewFactory.class).get(TimerView.class).startGameTimer();
}
}
// Force an update to occur. We don't really need to worry about data binding
// for something as straight forward as updating this view
update(new ViewEventArgs(StockView.this, ""));
}
});
addSignal(BacksideCardEntity.DECK_BACKSIDE_CHANGED, new ISignalReceiver() {
@Override public void signalReceived(EventArgs event) {
_stockCardEntities.stream().forEach(z -> z.refresh());
DeckAnimationHelper.getInstance().setScene(_stockCardEntities);
}
});
addSignal(DeckAnimationHelper.DECK_ANIMATION_UPDATED, new ISignalReceiver() {
@Override public void signalReceived(EventArgs event) {
//repaint();
}
});
}
@Override public void preprocessGraphics(IRenderable renderableData, Graphics context) {
super.preprocessGraphics(renderableData, context);
int index = -1;
for(StockCardEntity stockCardEntity : _stockCardEntities) {
if(stockCardEntity.equals(renderableData)) {
index = _stockCardEntities.indexOf(stockCardEntity);
break;
}
}
switch(index)
{
case 0:
this.renderProperties.canDraw = true;
this.renderProperties.x = 0;
this.renderProperties.y = 0;
this.renderProperties.width = CardView.CARD_WIDTH;
this.renderProperties.height = CardView.CARD_HEIGHT;
break;
case 1:
this.renderProperties.canDraw = _stockCardEntities.get(0).getBacksideVisible() && _talonView.isPhaseTwo();
this.renderProperties.x = 2;
this.renderProperties.y = 1;
this.renderProperties.width = CardView.CARD_WIDTH;
this.renderProperties.height = CardView.CARD_HEIGHT;
break;
case 2:
this.renderProperties.canDraw = _stockCardEntities.get(0).getBacksideVisible() && _talonView.isPhaseOne();
this.renderProperties.x = 4;
this.renderProperties.y = 2;
this.renderProperties.width = CardView.CARD_WIDTH;
this.renderProperties.height = CardView.CARD_HEIGHT;
break;
}
this.renderProperties.renderData = DeckAnimationHelper.getInstance().getRenderableContent();
}
@Override public Dimension getPreferredSize() {
return new Dimension(CardView.CARD_WIDTH + 4, CardView.CARD_HEIGHT + 2);
}
@Override public void render() {
super.render();
DeckAnimationHelper.getInstance().setScene(_stockCardEntities);
update(new ViewEventArgs(StockView.this, ""));
}
@Override public void update(EventArgs event) {
super.update(event);
addRenderableContent(DeckAnimationHelper.getInstance());
repaint();
}
@Override public void undoLastAction() {
_talonView.revertLastHand();
if(_talonView.isDeckPlayed()) {
_stockCardEntities.get(0).enableTalonRecycled();
}
else {
_stockCardEntities.remove(0);
_stockCardEntities.add(0, new StockCardEntity());
}
render();
}
@Override public void performBackup() {
// Not needed
}
@Override public void clearBackup() {
// Not needed
}
}
================================================
FILE: src/game/views/TableauPileView.java
================================================
package game.views;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import framework.api.IView;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.graphics.IRenderable;
import framework.core.mvc.view.PanelView;
import framework.core.physics.ICollidable;
import framework.utils.logging.Tracelog;
import game.config.OptionsPreferences;
import game.controllers.CardController;
import game.models.CardModel;
/**
* This view represents a single Tableau pile
*
* @author Daniel Ricci {@literal }
*/
public class TableauPileView extends AbstractPileView implements ICollidable {
/**
* The backside offset that the card should assume
*/
private final int CARD_OFFSET_BACKSIDE = 3;
/**
* The offset for each card when in this view
*/
private final int CARD_OFFSET = 15;
/**
* This panel is used as a placeholder within this view when there are no cards to be shown
*/
private final PanelView _noCardPanelView = new PanelView();
/**
* A reference to the options preferences resource
*/
private final OptionsPreferences _preferences = new OptionsPreferences();
/**
* Constructs a new instance of this class type
*/
private TableauPileView() {
// Force the rendering engine to attempt to render this view so that it can
// render the panel view when no cards are available and the player is in outline mode
setIsForceRendering(true);
setOpaque(true);
setBackground(new Color(0, 128, 0));
// Set the background of the panel and render it
_noCardPanelView.setBackground(Color.BLACK);
_noCardPanelView.render();
}
/**
* Constructs a new instance of this class type
*
* @param cards A list of card models to associate to this pile view
*/
public TableauPileView(List cards) {
this();
for(int i = 0; i < cards.size(); ++i) {
//Create the card view
cards.get(i).setBackside(i + 1 < cards.size());
CardView view = AbstractFactory.getFactory(ViewFactory.class).add(new CardView(cards.get(i)));
// Add the view to the layered pane
layeredPane.add(view);
layeredPane.setLayer(view, i);
// Set the bounds of the view within the layered pane
Point offset = getCardOffset(view);
view.setBounds(new Rectangle(offset.x, offset.y, view.getPreferredSize().width, view.getPreferredSize().height));
}
}
@Override public void preprocessGraphics(IRenderable renderableData, Graphics context) {
super.preprocessGraphics(renderableData, context);
if(getIsHighlighted() && layeredPane.getComponentCount() == 0) {
_preferences.load();
if(_preferences.outlineDragging) {
_noCardPanelView.setSize(new Dimension(CardView.CARD_WIDTH, CardView.CARD_HEIGHT));
_noCardPanelView.setPreferredSize(_noCardPanelView.getSize());
add(_noCardPanelView);
context.setXORMode(Color.WHITE);
}
}
else {
remove(_noCardPanelView);
}
}
@Override public void render() {
super.render();
for(Component component : layeredPane.getComponents()) {
if(component instanceof CardView) {
CardView view = (CardView) component;
view.render();
}
}
repaint();
}
@Override protected Point getCardOffset(CardView cardView) {
List cardComponents = Arrays.asList(layeredPane.getComponents());
Collections.reverse(cardComponents);
int index = cardComponents.indexOf(cardView);
if(index == -1) {
Tracelog.log(Level.SEVERE, true, "Cannot find the offset for the card " + cardView);
return new Point();
}
// If there is only one card then it will have an offset of 0
if(index == 0) {
return new Point();
}
// Get a reference to the previous card and it's bounds
CardView previousCard = (CardView)cardComponents.get(index - 1);
Rectangle previousCardBounds = previousCard.getBounds();
// If the card before this one has it's backside showing, then it should be
// positioned 3 pixels lower than that card
if(previousCard.isBacksideShowing()) {
return new Point(0, previousCardBounds.y + CARD_OFFSET_BACKSIDE);
}
// Subsequent cards should be 12 pixels down from the previous card
else {
return new Point(0, previousCardBounds.y + CARD_OFFSET);
}
}
@Override public boolean isValidCollision(Component source) {
// If there are no components then only allow a king to be placed
if(layeredPane.getComponentCount() == 0) {
IView cardView = (IView)source;
boolean isValidCollision = cardView.getViewProperties().getEntity(CardController.class).getCard().getCardEntity().isCardKing();
return isValidCollision;
}
if(!(layeredPane.getComponent(0) instanceof CardView) ){
return false;
}
// Get the bottom most card within the pile view.
CardView cardView = (CardView) layeredPane.getComponent(0);
// Get the bounds associated to this pile view
Rectangle thisBounds = this.getBounds();
Rectangle thatBounds = cardView.getBounds();
Rectangle rect = new Rectangle(
thisBounds.x + thatBounds.x,
thisBounds.y + thatBounds.y,
source.getWidth(),
source.getHeight()
);
// If the intersection is valid then verify if the card allows
// for the collision
if(source.getBounds().intersects(rect)) {
return cardView.isValidCollision(source);
}
return false;
}
@Override public void onCollisionStart(Component source) {
CardView cardView = this.getLastCard();
if(cardView != null) {
cardView.onCollisionStart(source);
}
}
@Override public void onCollisionStop(Component source) {
CardView cardView = this.getLastCard();
if(cardView != null) {
cardView.onCollisionStop(source);
}
}
}
================================================
FILE: src/game/views/TalonPileView.java
================================================
package game.views;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.JLayeredPane;
import javax.swing.SwingUtilities;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ControllerFactory;
import framework.core.factories.ViewFactory;
import framework.core.mvc.view.PanelView;
import framework.core.physics.ICollidable;
import framework.utils.MouseListenerEvent;
import framework.utils.MouseListenerEvent.SupportedActions;
import framework.utils.logging.Tracelog;
import game.config.OptionsPreferences;
import game.config.OptionsPreferences.DrawOption;
import game.config.OptionsPreferences.ScoringOption;
import game.controllers.MovementRecorderController;
import game.models.CardModel;
import game.views.helpers.ViewHelper;
/**
* This views represents the talon pile view. This view will display cards whenever the user clicks on
* the Stock view. This view will adapt itself based on the options set (draw three vs. draw one)
*
* @author Daniel Ricci {@literal }
*/
public final class TalonPileView extends AbstractPileView implements ICollidable {
/**
* Specifies the offset of each card within this view
*/
private int CARD_OFFSET_X = 12;
/**
* The available states of the talon
*
* @author Daniel Ricci {@literal }
*/
public enum TalonCardState {
// An Empty Deck
EMPTY,
// The Deck has been played through
DECK_PLAYED,
// Normal card played
NORMAL
}
/**
* A helper class for holding onto a layer and it's associated card state
*
* @author Daniel Ricci {@literal }
*/
private class TalonCardReference {
/**
* The layer of the last card that was clicked
*/
public final int layer;
/**
* The last card that was clicked w.r.t this view
*/
public final CardView card;
/**
* Constructs a new instance of this class type
*
* @param card A card view
* @param layer A specified layer
*/
public TalonCardReference(CardView card, int layer) {
this.card = card;
this.layer = layer;
}
/**
* Constructs a new instance of this class type
*
* @param card A card view
*/
public TalonCardReference(CardView card) {
this.card = card;
this.layer = JLayeredPane.getLayer(card);
}
@Override public String toString() {
return String.format("Layer: %s | Card: %s", this.layer, this.card);
}
}
/**
* The total number of cards that this view contains by default
*/
public static final int TOTAL_CARD_SIZE = 24;
/**
* The last card hand state of this talon
*/
private TalonCardState _lastCardHandState = null;
/**
* The number of times that the deck was played
*/
private int _deckPlays;
/**
* This flag indicates if the deck is in a recycled state
*/
private boolean _isDeckInRecycledState;
/**
* The blank card associated to the talon view
*/
private final PanelView _blankCard = new PanelView();
/**
* The last card that was interacted with
*/
private TalonCardReference _lastCardInteracted = null;
/**
* The card that can be undone
*/
private TalonCardReference _undoableCard = null;
/**
* Constructs a new instance of this class type
*/
private TalonPileView() {
_blankCard.setBackground(new Color(0, 128, 0));
// Arbitrary number, big enough to do some damage
_blankCard.setPreferredSize(new Dimension(1000, 1000));
_blankCard.setBounds(new Rectangle(0, 0, _blankCard.getPreferredSize().width, _blankCard.getPreferredSize().height));
_blankCard.setVisible(true);
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(preferences.drawOption == DrawOption.THREE) {
CARD_OFFSET_X = 12;
}
else {
CARD_OFFSET_X = 0;
}
// The blank card will always be in this view, so right clicking on it should autocomplete
// whatever is on the board
ViewHelper.registerForCardsAutocomplete(_blankCard);
// Add a listener to the blank card since it is sitting above the board. If someone tries to click in this area
// the timer will start, unknowing to the player that they really clicked on a special area of the board
_blankCard.addMouseListener(new MouseAdapter() {
@Override public void mousePressed(MouseEvent event) {
if(!SwingUtilities.isRightMouseButton(event)) {
AbstractFactory.getFactory(ViewFactory.class).get(TimerView.class).startGameTimer();
_blankCard.removeMouseListener(this);
}
}
});
}
/**
* Constructs a new instance of this class type
*
* @param cards The card models to load within this view
*/
public TalonPileView(List cards) {
this();
if(cards.size() > TOTAL_CARD_SIZE) {
Tracelog.log(Level.SEVERE, true, "Talon has been allocated more than the currently set max card size that can be allocated!");
}
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
for(int i = 0, layer = 0; i < cards.size(); ++i) {
CardView cardView = AbstractFactory.getFactory(ViewFactory.class).add(new CardView(cards.get(i)));
MouseListenerEvent adapter = new MouseListenerEvent(SupportedActions.LEFT) {
@Override public void mousePressed(MouseEvent event) {
super.mousePressed(event);
if(event.isConsumed() && getIsConsumed()) {
return;
}
// Do not allow non-enabled cards to run
if(!cardView.isEnabled()) {
return;
}
// Take the card that was pressed on and record it's layer location
_lastCardInteracted = new TalonCardReference(cardView);
}
@Override public void mouseReleased(MouseEvent event) {
super.mouseReleased(event);
if(event.isConsumed() && getIsConsumed()) {
return;
}
// Prevent other released events from being called by other cards that are not yet enabled
if(!cardView.isEnabled()) {
return;
}
// If the card is no longer associated to the talon then attempt to get the next one
if(!(cardView.getParentIView() instanceof TalonPileView)) {
// The top-most card cannot be the layered pane
boolean cond1 = layeredPane.highestLayer() != JLayeredPane.getLayer(_blankCard);
// There must not be any more visible cards (excluding the blank card)
boolean cond2 = Arrays.asList(layeredPane.getComponents()).stream().anyMatch(z -> !z.equals(_blankCard) && z.isVisible());
if(cond1 && !cond2) {
for(int iterations = 0, layerId = JLayeredPane.getLayer(_blankCard) + 1; layerId <= layeredPane.highestLayer() || iterations < 3; ++layerId, ++iterations) {
Component component = layeredPane.getComponentsInLayer(layerId)[0];
component.setVisible(true);
}
}
}
// The card was put back, so position it accordingly so that it can be shown again
// Make sure that the card is enabled. Since when a card is not enabled, the event
// handlers are not applied to the card
else if(cardView.isEnabled()){
// If the blank card is on the same layer as this card, put this card to the next layer above.
// This could only occur if this was already top-most
if(JLayeredPane.getLayer(_blankCard) == JLayeredPane.getLayer(cardView)) {
layeredPane.setLayer(cardView, JLayeredPane.getLayer(cardView) + 1);
}
setBounds(cardView);
}
// When the mouse is released, ensure that the component located at the highest layer is enabled
layeredPane.getComponentsInLayer(layeredPane.highestLayer())[0].setEnabled(true);
}
};
cardView.addMouseListener(adapter);
cardView.getOutlineView().addMouseListener(adapter);
// Set the default bounds of the card
cardView.setBounds(new Rectangle(0, 0, cardView.getPreferredSize().width, cardView.getPreferredSize().height));
// All cards are disabled by default, and should be disabled by default after a subsequent deck has been played through
cardView.setEnabled(false);
// Add the card to the layered pane and set it's layer accordingly
layeredPane.add(cardView);
if(preferences.drawOption == DrawOption.THREE) {
layeredPane.setLayer(cardView, layer / 3);
++layer;
}
else {
layeredPane.setLayer(cardView, i);
}
}
// Add the blank card last, and make sure that it has the topmost layer
layeredPane.add(_blankCard);
layeredPane.setLayer(_blankCard, layeredPane.highestLayer() + 1);
}
/**
* @return TRUE if the pile style has not yet gone through 4 cards, FALSE otherwise
*/
public boolean isPhaseOne() {
return getBlankCardPosition() < 4 - getCardsRemainingCount();
}
/**
* @return TRUE if the pile style has not yet gone through 14 cards, FALSE otherwise
*/
public boolean isPhaseTwo() {
return getBlankCardPosition() < 14 - getCardsRemainingCount();
}
/**
* @return TRUE if the Talon has been played through fully, FALSE otherwise
*/
public boolean isDeckPlayed() {
return layeredPane.lowestLayer() == JLayeredPane.getLayer(_blankCard);
}
/**
* @return TRUE if the Talon has gone through the specified number
* of deck shuffles (based on the options preferences currently set), FALSE otherwise
*/
public boolean isTalonEnded() {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(preferences.drawOption == DrawOption.ONE && preferences.scoringOption == ScoringOption.VEGAS && _deckPlays == 1) {
return true;
}
if(preferences.drawOption == DrawOption.THREE && preferences.scoringOption == ScoringOption.VEGAS && _deckPlays == 3) {
return true;
}
return false;
}
/**
* @return The current state of the Talon based on the last operation played
*/
public TalonCardState getState() {
return _lastCardHandState;
}
/**
* Reverts the last hand played
*/
public void revertLastHand() {
if(JLayeredPane.getLayer(_blankCard) == layeredPane.highestLayer()) {
_isDeckInRecycledState = false;
layeredPane.setLayer(_blankCard, layeredPane.lowestLayer() - 1);
}
else {
if(JLayeredPane.getLayer(_blankCard) == layeredPane.lowestLayer()) {
_deckPlays = Math.max(0, --_deckPlays);
}
// Disable all the cards
Arrays.asList(layeredPane.getComponents()).forEach(z -> z.setEnabled(false));
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(preferences.drawOption == DrawOption.ONE) {
// Get the top-most component and set it underneath the blank card.
Component comp = layeredPane.getComponentsInLayer(layeredPane.highestLayer())[0];
comp.setVisible(false);
layeredPane.setLayer(comp, JLayeredPane.getLayer(_blankCard));
layeredPane.getComponentsInLayer(layeredPane.highestLayer())[0].setEnabled(true);
this.setBounds(comp);
// From the bottom upwards, re-order the layer of each card
Component[] components = layeredPane.getComponents();
for(int layer = 0, i = components.length - 1; i >= 0; --i, ++layer) {
layeredPane.setLayer(components[i], layer);
}
}
else {
// From the blank card upwards, up each components layer by 1
int blankCardLayer = JLayeredPane.getLayer(_blankCard);
Component[] components = layeredPane.getComponents();
for(Component comp : components) {
if(layeredPane.getLayer(comp) >= blankCardLayer) {
layeredPane.setLayer(comp, layeredPane.getLayer(comp) + 1);
}
}
// Take the top three cards and move them to the original blank card index
//
// Note: Because of how draw three works, we need to do this after all the cards of the layer have had
// their visibility properly set to false
Component[] cardsBeingReverted = layeredPane.getComponentsInLayer(layeredPane.highestLayer());
for(Component comp : cardsBeingReverted) {
layeredPane.setLayer(comp, blankCardLayer);
comp.setVisible(false);
}
Arrays.asList(cardsBeingReverted).stream().filter(z -> z instanceof CardView).forEach(z -> this.setBounds(z));
// Take the highest layered cards at this point, set their screen position accordingly, and enable
// the first card to be moved
Component[] highestCards = layeredPane.getComponentsInLayer(layeredPane.highestLayer());
// If the card is not the blank card, then remove the cards and re-add them, they will
// go through the process of being properly re-positioned
if(!(highestCards.length == 1 && highestCards[0] == _blankCard)) {
for(Component comp : highestCards) {
setBounds(comp);
}
highestCards[0].setEnabled(true);
}
}
}
}
/**
* Displays the next card hand on this view
*/
public void cycleNextHand() {
// If the talon can no longer be played with, then go no futher
if(isTalonEnded()) {
_lastCardHandState = TalonCardState.DECK_PLAYED;
return;
}
// If there is only one component then dont go further. The idea is that the "blank" placeholder view that
// mimics that switching of cards should never be removed from this view, thus if that is the only view that
// exists then it should mean that all the playing cards having been removed from this view
if(layeredPane.getComponentCount() == 1) {
Tracelog.log(Level.INFO, true, "There are no more cards left in the Talon to play.");
_lastCardHandState = TalonCardState.EMPTY;
return;
}
// Notify the movement controller that there was a movement that occured of the talon, from the stock view
AbstractFactory.getFactory(ControllerFactory.class).get(MovementRecorderController.class).recordMovement(AbstractFactory.getFactory(ViewFactory.class).get(StockView.class), this);
// If we are in a recycle deck state then recycle the deck
if(_isDeckInRecycledState) {
layeredPane.setLayer(_blankCard, layeredPane.lowestLayer() - 1);
recycleDeck();
}
// If we are at the end then restart the deck
if(JLayeredPane.getLayer(_blankCard) == layeredPane.lowestLayer()) {
// New deck has the score updated
AbstractFactory.getFactory(ViewFactory.class).get(ScoreView.class).updateScoreDeckFinished(_deckPlays);
// The next pass will recycle the deck
_isDeckInRecycledState = true;
// Mask the top layer of the deck to make it appear that the deck has reshuffled, this is to make undoing at this
// stage a million times easier, just flip the card back to the bottom
layeredPane.setLayer(_blankCard, layeredPane.highestLayer() + 1);
}
else {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
// Disable all the cards
Arrays.asList(layeredPane.getComponents()).forEach(z -> z.setEnabled(false));
if(preferences.drawOption == DrawOption.ONE) {
// Get the card that is directly below the blank card
Component cardDirectlyBelowBlankCard = layeredPane.getComponent(layeredPane.getIndexOf(_blankCard) + 1);
cardDirectlyBelowBlankCard.setVisible(true);
// Set the layer of the card that is directly below the blank card, to the highest layer
layeredPane.setLayer(cardDirectlyBelowBlankCard, layeredPane.highestLayer() + 1);
cardDirectlyBelowBlankCard.setEnabled(true);
this.setBounds(cardDirectlyBelowBlankCard);
resyncDeckLayers();
}
else {
// Reposition all the cards to the origin
repositionCardsAboveBlankCardToOrigin();
// Take the blank card and position to the layer one below. Take that layer where the blank card is at
// and make the other cards top most. Also make sure to position them correctly and set their bounds
int blankCardLayerIndex = JLayeredPane.getLayer(_blankCard);
int cardsUnderBlankCard = blankCardLayerIndex - 1;
int highestLayer = layeredPane.highestLayer();
Component[] components = layeredPane.getComponentsInLayer(cardsUnderBlankCard);
for(Component component : components) {
component.setVisible(true);
component.setEnabled(components[0].equals(component));
layeredPane.setLayer(component, highestLayer + 1);
this.setBounds(component);
}
// Note: Set the layer of the blank card after the positioning of the other cards occur, or the blank card
// will also be moved back to top, not something we want to do.
layeredPane.setLayer(_blankCard, cardsUnderBlankCard);
resyncDeckLayers();
}
if(layeredPane.lowestLayer() == JLayeredPane.getLayer(_blankCard)) {
++_deckPlays;
_lastCardHandState = TalonCardState.DECK_PLAYED;
return;
}
}
_lastCardHandState = TalonCardState.NORMAL;
}
/**
* @return The position of where the deck of this talon is CURRENTLY is at, which is
* based on the blank card location within all the components in the layered pane
*/
private int getBlankCardPosition() {
return layeredPane.getIndexOf(_blankCard);
}
/**
* Gets the deck position of the specified component, w.r.t the business logic ordering of the deck
*
* @param component The component
*
* @return The deck position within the deck
*/
private int getPosition(Component component) {
int position = 0;
List components = Arrays.asList(layeredPane.getComponents());
Collections.reverse(components);
if(!component.isVisible()) {
// Take the total number of cards in the layered pane, subtract 1 to remove the special hidden
// card, and subtract the index of the component.
position = layeredPane.getComponentCount() - components.indexOf(component) - 1;
}
else {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(preferences.drawOption == DrawOption.ONE) {
position = components.stream().filter(z -> z.isVisible() && z instanceof CardView).collect(Collectors.toList()).indexOf(component) + 1;
}
else {
// Get the list of layers, and make sure they are totally ordered
SortedSet uniqueLayers = new TreeSet();
components.stream().filter(z -> z.isVisible() && z instanceof CardView).forEach(z -> uniqueLayers.add(layeredPane.getLayer(z)));
List componentsOrdered = new ArrayList();
for(int uniqueLayer : uniqueLayers) {
componentsOrdered.addAll(Arrays.asList(layeredPane.getComponentsInLayer(uniqueLayer)));
}
position = componentsOrdered.indexOf(component) + 1;
}
}
return position;
}
/**
* @return The number of cards remaining based on the original count of this view
*/
private int getCardsRemainingCount() {
return TOTAL_CARD_SIZE - (layeredPane.getComponentCount() - 1);
}
/**
* Recycles the deck
*/
private void recycleDeck() {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
// Remove the blank card from the layered pane, put it back at the end, much easier
layeredPane.remove(_blankCard);
// The list of components
Component[] components = layeredPane.getComponents();
if(preferences.drawOption == DrawOption.ONE) {
for(int i = components.length - 1; i >= 0; --i) {
Component component = components[i];
layeredPane.setLayer(component, i);
component.setEnabled(false);
component.setVisible(false);
}
}
else {
for(int i = 0; i < components.length; ++i) {
Component component = components[i];
layeredPane.setLayer(component, i / 3);
component.setEnabled(false);
component.setVisible(false);
}
}
// Add back the blank card as the top-most card
layeredPane.add(_blankCard);
layeredPane.setLayer(_blankCard, layeredPane.highestLayer() + 1);
// Update the flag indicating that the deck is recycled
_isDeckInRecycledState = false;
}
/*
* Re-syncs the deck, ensuring that the layers are sequentially ordered
*/
private void resyncDeckLayers() {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(preferences.drawOption == DrawOption.ONE) {
// Starting from the lowest layer upwards, re-synchronize all the layer positions of the cards.
for(int i = layeredPane.getComponentCount() - 1, layerId = 0; i >= 0; --i, ++layerId) {
// Re-synchronize the layer position of the card
layeredPane.setLayer(layeredPane.getComponent(i), layerId);
}
}
else if(preferences.drawOption == DrawOption.THREE) {
// Get the list of components grouped by their layer
List componentsGroupedByLayer = getComponentsGroupedByLayer();
// Re-input components by their layer, ensuring that their layer is ordered
// sequentially WITHOUT any gaps between layer numbers
for(int i = 0; i < componentsGroupedByLayer.size(); ++i) {
Component[] components = componentsGroupedByLayer.get(i);
for(int j = 0; j < components.length; ++j) {
layeredPane.setLayer(components[j], i);
}
}
}
}
/**
* Repositions the list of cards within the layered pane so that the ordering is sequential
*
* (Draw Three Specific)
*/
private void repositionCardsAboveBlankCardToOrigin() {
// Before proceeding, everything that has a higher layer than the blank card needs to be re-positioned to the origin
if(layeredPane.highestLayer() != JLayeredPane.getLayer(_blankCard)) {
int layer = layeredPane.highestLayer();
while(layer != JLayeredPane.getLayer(_blankCard)) {
List components = Arrays.asList(layeredPane.getComponentsInLayer(layer));
for(Component component : components) {
// For the draw one implementation style
this.setBoundsDrawOneImpl(component, getPosition(component));
}
--layer;
}
}
}
/**
* Sets the bounds of the specified component
*
* @param component The component to set bounds
*/
private void setBounds(Component component) {
// Calculate the deck position of the specified component
int position = getPosition(component);
// The position of the card when playing with `three` is all that concerns us since position matters, vs `single` card which are all stacked.
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(preferences.drawOption == DrawOption.THREE) {
setBoundsDrawThreeImpl(component, position);
}
else {
setBoundsDrawOneImpl(component, position);
}
}
/**
* Implementation of setting the bounds of the specified component at the specified position
* when the game is in `Draw Three` mode
*
* @param component The component
* @param position The position
*/
private void setBoundsDrawOneImpl(Component component, int position) {
int x = 0;
int y = 0;
if(position < 12) {
x = 0;
y = 0;
}
else if (position < 22) {
x = 2;
y = 1;
}
else {
x = 4;
y = 2;
}
// Set the default bounds of the card view
Rectangle bounds = new Rectangle(x, y, component.getPreferredSize().width, component.getPreferredSize().height);
component.setBounds(bounds);
}
/**
* Implementation of setting the bounds of the specified component at the specified position
* when the game is in `Draw Three` mode
*
* @param component The component
* @param position The position
*/
private void setBoundsDrawThreeImpl(Component component, int position) {
int xModifier = 0;
int yModifier = 0;
if(position < 13) {
xModifier = 0;
yModifier = 0;
}
else if(position < 22) {
xModifier = 2;
yModifier = 1;
}
else {
xModifier = 4;
yModifier = 2;
}
// Re-order the cards that exist currently, before determining where this card should be placed.
Component[] components = layeredPane.getComponentsInLayer(layeredPane.getLayer(component));
for(int i = components.length - 1; i >= 0; --i) {
Rectangle bounds = new Rectangle(0, 0, component.getPreferredSize().width, component.getPreferredSize().height);
int positionIndex = layeredPane.getPosition(components[i]);
int offset = 3 - components.length;
switch(positionIndex + offset) {
case 0:
bounds.x = 2 * CARD_OFFSET_X;
bounds.y = 2;
break;
case 1:
bounds.x = CARD_OFFSET_X;
bounds.y = 1;
break;
case 2:
bounds.x = 0;
bounds.y = 0;
break;
}
bounds.x += xModifier;
bounds.y += yModifier;
// Set the new bounds of the specified component
components[i].setBounds(bounds);
}
}
@Override public void addCard(CardView cardView) {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(preferences.drawOption == DrawOption.THREE) {
addCard(cardView, _lastCardInteracted.layer);
layeredPane.setPosition(cardView, 0);
}
else {
super.addCard(cardView);
}
}
@Override public String toString() {
StringBuilder builder = new StringBuilder();
String header = "========" + this.getClass().getSimpleName().toUpperCase() + "========";
builder.append(header + System.getProperty("line.separator"));
int blankLayer = JLayeredPane.getLayer(_blankCard);
JLayeredPane blankParentLayeredPane = (JLayeredPane) _blankCard.getParent();
List components = Arrays.asList(blankParentLayeredPane.getComponentsInLayer(blankLayer));
int blankPositionWithinlayer = components.indexOf(_blankCard);
for(Component comp : layeredPane.getComponents()) {
if(comp instanceof CardView) {
builder.append(comp + System.getProperty("line.separator"));
}
else if(comp.equals(_blankCard)) {
builder.append("===BLANK CARD===\t[" + blankLayer + "][" + blankPositionWithinlayer + "]" + System.getProperty("line.separator"));
}
}
builder.append(System.getProperty("line.separator"));
builder.append("Decks Played: " + _deckPlays + System.getProperty("line.separator"));
builder.append("Last Card Hand State: " + _lastCardHandState + System.getProperty("line.separator"));
builder.append("Undoable Card: " + String.valueOf(_undoableCard) + System.getProperty("line.separator"));
builder.append("Last Card Interacted: " + String.valueOf(_lastCardInteracted) + System.getProperty("line.separator"));
builder.append("Is Deck In Recycle State: " + String.valueOf(_isDeckInRecycledState) + System.getProperty("line.separator"));
builder.append(System.getProperty("line.separator"));
builder.append(new String(new char[header.length()]).replace("\0", "="));
return builder.toString();
}
@Override public void render() {
super.render();
for(Component comp : layeredPane.getComponents()) {
if(!comp.equals(_blankCard)) {
comp.setVisible(false);
}
}
}
@Override public boolean isValidCollision(Component source) {
return false;
}
@Override public void undoLastAction() {
// If there was a card recoreded
if(_undoableCard != null ) {
// Get the highest component and set the enabled flag to so that it does not move anymore
Component highestComponent = layeredPane.getComponentsInLayer(layeredPane.highestLayer())[0];
highestComponent.setEnabled(false);
// This is required to be done, because when we call `addCard` below, it uses the `_lastCardInteracted`, so we
// update this value accordingly
TalonCardReference temp = _lastCardInteracted;
_lastCardInteracted = _undoableCard;
addCard(_undoableCard.card);
_lastCardInteracted = temp;
// Set the bounds of the card
setBounds(_undoableCard.card);
repaint();
}
}
@Override public void performBackup() {
// Get the card that is owned by the game view. When a drag occurs, the card is owned by the game view so that
// it can be freely dragged around the entire game.
CardView cardView = AbstractFactory.getFactory(ViewFactory.class).get(GameView.class).getCardComponent();
// If the card cannot be found and if the talon doesnt have the blank card as the top most card
if(cardView == null && layeredPane.highestLayer() != JLayeredPane.getLayer(_blankCard)) {
// Take the card that is at the top-most of the talon. This is the case when we are
// playing in outline mode, and the card still exists on the talon, because the card outline
// is the thing that is actually movings
cardView = (CardView) layeredPane.getComponentsInLayer(layeredPane.highestLayer())[0];
}
// If no card was specified and a backup is required, take the top most
// card. This can only happen if an automove occurs
if(_lastCardInteracted == null) {
_lastCardInteracted = new TalonCardReference(getLastCard());
}
// Set the undoable card as the card that can be undone
_undoableCard = new TalonCardReference(cardView, _lastCardInteracted.layer);
}
@Override public void clearBackup() {
_undoableCard = null;
}
@Override protected Point getCardOffset(CardView cardView) {
// Not needed
return new Point(0, 0);
}
@Override public void onCollisionStart(Component source) {
// Not needed
}
@Override public void onCollisionStop(Component source) {
// Not needed
}
}
================================================
FILE: src/game/views/TimerView.java
================================================
package game.views;
import java.awt.Color;
import java.awt.FlowLayout;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JLabel;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.mvc.view.PanelView;
public final class TimerView extends PanelView {
private Timer _timer = new Timer(true);
private JLabel _label = new JLabel();
private long _time = 0;
private boolean _running;
private boolean _cancelled;
/**
* Constructs a new instance of this class type
*/
public TimerView() {
this.setBackground(Color.WHITE);
_label.setText(this.toString());
add(_label);
// Set the VGap so that the time renders appropriately
FlowLayout layout = (FlowLayout) this.getLayout();
layout.setVgap(0);
}
public void startGameTimer() {
if(_cancelled || _running || !isVisible()) {
return;
}
_running = true;
_time = 0;
_timer.schedule(new TimerTask() {
@Override public void run() {
++_time;
_label.setText(TimerView.this.toString());
if(_time % 10 == 0) {
AbstractFactory.getFactory(ViewFactory.class).get(ScoreView.class).updateScoreTimerTick();
}
}
}, 1000, 1000);
}
public long getTime() {
return _time;
}
public void stop() {
_cancelled = true;
_timer.cancel();
_running = false;
}
@Override public void destructor() {
_cancelled = true;
_timer.cancel();
super.destructor();
}
@Override public String toString() {
return "Time: " + String.valueOf(_time);
}
}
================================================
FILE: src/game/views/VegasScoreView.java
================================================
package game.views;
import java.awt.Color;
import java.util.logging.Level;
import framework.core.system.Application;
import framework.utils.logging.Tracelog;
import game.config.OptionsPreferences;
import game.models.MovementModel.MovementType;
/**
* This view shows the game score when playing in a vegas styled environment
*
* @author Daniel Ricci {@literal }
*/
public class VegasScoreView extends ScoreView {
/**
* Constructs a new instance of this class type
*/
public VegasScoreView() {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(preferences.cumulativeScore) {
SCORE_CURRENT += -52;
}
else {
SCORE_CURRENT = -52;
}
}
@Override protected void addToScore(long score) {
SCORE_CURRENT += score;
scoreValue.setText(toString());
}
@Override public long updateScoreBonus(long seconds) {
return 0;
}
@Override public void updateScoreDeckFinished(int deckPlays) {
}
@Override public void updateScoreTimerTick() {
}
@Override public void updateScoreCardTurnOver() {
}
@Override protected void updateScore(MovementType from, MovementType to, boolean isUndo) {
long scoreBefore = SCORE_CURRENT;
if(from == MovementType.TALON && to == MovementType.TABLEAU) {
addToScore(isUndo ? -5 : 5);
}
else if(from == MovementType.TALON && to == MovementType.FOUNDATION) {
addToScore(isUndo ? -5 : 5);
}
else if (from == MovementType.TABLEAU && to == MovementType.FOUNDATION) {
addToScore(isUndo ? -5 : 5);
}
else if(from == MovementType.FOUNDATION && to == MovementType.TABLEAU) {
addToScore(isUndo ? 5 : -5);
}
else {
return;
}
Tracelog.log(Level.INFO, true, String.format("Score %s: Changed from %d to %d after performing move [%s] to [%s]", isUndo ? "Undo" : "Updated",scoreBefore, SCORE_CURRENT, from, to));
}
@Override public void destructor() {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
if(!preferences.cumulativeScore || Application.instance.isRestarting) {
super.destructor();
}
}
@Override public String toString() {
if(SCORE_CURRENT < 0) {
scoreValue.setForeground(Color.RED);
}
else {
scoreValue.setForeground(Color.BLACK);
}
String result = "$";
if(SCORE_CURRENT < 0) {
result = "-" + result;
}
return result + String.valueOf(Math.abs(SCORE_CURRENT));
}
}
================================================
FILE: src/game/views/components/ExclusiveLineBorder.java
================================================
package game.views.components;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import javax.swing.border.LineBorder;
/**
* Creates a line border that attempts to render it's edges by applying an XOR bitmask with
* the underlying pixel color (per-pixel precision)
*
* @author Daniel Ricci {@literal }
*
*/
public class ExclusiveLineBorder extends LineBorder {
/**
* Constructs a new instance of this class type
*
* @param thickness The thickness of the borders
*/
public ExclusiveLineBorder(int thickness) {
super(Color.BLACK, thickness, true);
}
@Override public void paintBorder(Component component, Graphics graphic, int x, int y, int width, int height) {
graphic.setXORMode(Color.WHITE);
super.paintBorder(component, graphic, x, y, width, height);
}
}
================================================
FILE: src/game/views/helpers/DeckAnimationHelper.java
================================================
package game.views.helpers;
import java.awt.Image;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.stream.Collectors;
import framework.communication.internal.signal.arguments.EventArgs;
import framework.core.factories.AbstractFactory;
import framework.core.factories.DataFactory;
import framework.core.factories.ViewFactory;
import framework.core.graphics.IRenderable;
import framework.core.graphics.IRenderableContainer;
import framework.utils.logging.Tracelog;
import game.entities.StockCardEntity;
import game.views.StockView;
import generated.DataLookup;
public class DeckAnimationHelper implements IRenderableContainer {
private List stockCardEntities;
private List deckImageAnimations;
private Image deckImageOriginal;
private Image currentDeckImageAnimation;
private int delay = 0;
private int period = 1000;
private Timer timer;
public static final String DECK_ANIMATION_UPDATED = "DECK_ANIMATION_UPDATED";
private static volatile DeckAnimationHelper instance = null;
private DeckAnimationHelper() {
}
public static DeckAnimationHelper getInstance() {
synchronized(DeckAnimationHelper.class) {
if(instance == null) {
instance = new DeckAnimationHelper();
}
}
return instance;
}
public void clear() {
if(timer != null) {
timer.cancel();
timer = null;
}
this.currentDeckImageAnimation = null;
this.deckImageOriginal = null;
this.stockCardEntities = null;
}
public void setScene(List stockCardEntities) {
this.clear();
this.stockCardEntities = stockCardEntities;
StockCardEntity stockCardEntity = stockCardEntities.get(0);
if(stockCardEntity.getActiveDataIdentifier() == DataLookup.MISC.TALON_RESTART.identifier || stockCardEntity.getActiveDataIdentifier() == DataLookup.MISC.TALON_END.identifier) {
this.deckImageOriginal = stockCardEntity.getRenderableContent();
return;
}
Image deckImageOriginal = stockCardEntities.get(0).backsideCardEntity.getRenderableContent();
if(this.deckImageOriginal == deckImageOriginal) {
return;
}
this.deckImageAnimations = new ArrayList();
switch(stockCardEntities.get(0).backsideCardEntity.getBacksideData()) {
case DECK_7: // ROBOT
this.deckImageAnimations.addAll(
AbstractFactory.getFactory(DataFactory.class).getDataEntities(
DataLookup.ANIMATIONS.DECK_7_2.identifier,
DataLookup.ANIMATIONS.DECK_7_1.identifier,
null,
DataLookup.ANIMATIONS.DECK_7_1.identifier
));
this.period = 800;
this.delay = 0;
break;
case DECK_10: // CASTLE
this.deckImageAnimations.addAll(AbstractFactory.getFactory(DataFactory.class).getDataEntities(
null,
DataLookup.ANIMATIONS.DECK_10_1.identifier
));
this.period = 1000;
this.delay = 0;
break;
case DECK_11: // BEACH
this.deckImageAnimations.addAll(AbstractFactory.getFactory(DataFactory.class).getDataEntities(
null,
DataLookup.ANIMATIONS.DECK_11_1.identifier,
DataLookup.ANIMATIONS.DECK_11_2.identifier
));
this.period = 1500;
this.delay = 8500;
break;
case DECK_12: // POKER
this.deckImageAnimations.addAll(AbstractFactory.getFactory(DataFactory.class).getDataEntities(
null,
DataLookup.ANIMATIONS.DECK_12_1.identifier,
DataLookup.ANIMATIONS.DECK_12_2.identifier,
DataLookup.ANIMATIONS.DECK_12_1.identifier
));
this.period = 1000;
this.delay = 9000;
break;
default:
break;
}
this.deckImageOriginal = deckImageOriginal;
this.currentDeckImageAnimation = null;
this.timer = new Timer(true);
this.timer.schedule(new TimerTask() {
private int index = 0;
@Override public void run() {
//System.out.println(index + " of " + (deckImageAnimations.size() - 1));
if(deckImageAnimations.isEmpty()) {
return;
}
currentDeckImageAnimation = deckImageAnimations.get(index);
AbstractFactory.getFactory(ViewFactory.class).multicastSignalListeners(StockView.class, new EventArgs(this, DECK_ANIMATION_UPDATED));
if(index == 0) {
//System.out.println("Sleeping for " + delay + " milliseconds");
try {
Thread.sleep(delay);
} catch (Exception exception){
Tracelog.log(Level.WARNING, true, exception);
}
}
index = (index + 1) % deckImageAnimations.size();
}
}, 0, period);
}
@Override public List getRenderableContents() {
return this.stockCardEntities.stream().map(z -> z.toRenderable()).collect(Collectors.toList());
}
@Override public Image getRenderableContent() {
if(this.currentDeckImageAnimation == null) {
return this.deckImageOriginal;
}
return this.currentDeckImageAnimation;
}
}
================================================
FILE: src/game/views/helpers/ViewHelper.java
================================================
package game.views.helpers;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.SwingUtilities;
import framework.api.IView;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.utils.MouseListenerEvent;
import framework.utils.MouseListenerEvent.SupportedActions;
import game.views.CardView;
import game.views.TableauPileView;
import game.views.TalonPileView;
/**
* Helper class for common view related functionality
*
* @author Daniel Ricci {@literal }
*/
public class ViewHelper {
/**
* Registers the specified view so that it can initiate an Autocomplete opertation
*
* @param view The specified view to register this event onto
*/
public static void registerForCardsAutocomplete(IView view) {
view.getContainerClass().addMouseListener(new MouseListenerEvent(SupportedActions.RIGHT) {
@Override public void mousePressed(MouseEvent event) {
super.mousePressed(event);
if(this.getIsConsumed()) {
return;
}
if(SwingUtilities.isLeftMouseButton(event)) {
return;
}
performCardsAutocomplete();
}
});
}
/**
* Performs an auto complete based on all available cards
*/
private static void performCardsAutocomplete() {
ViewFactory viewFactory = AbstractFactory.getFactory(ViewFactory.class);
List cards = new ArrayList();
// Get the top-most talon card
CardView talonCard = viewFactory.get(TalonPileView.class).getLastCard();
if(talonCard != null) {
cards.add(talonCard);
}
// Get all available top-most front-facing cards
List tableauPileViews = viewFactory.getAll(TableauPileView.class);
Collections.reverse(tableauPileViews);
for(TableauPileView view : tableauPileViews) {
CardView card = view.getLastCard();
if(card != null && !card.isBacksideShowing()) {
cards.add(card);
}
}
// Go through the list and apply the automove on each card until there are no cards
// left or all cards have been iterated over
while(cards.size() > 0) {
boolean keepGoing = false;
for(int i = 0; i < cards.size(); ++i) {
if(cards.get(i).performCardAutoMovement()) {
cards.remove(i);
keepGoing = true;
break;
}
}
if(!keepGoing) {
break;
}
}
// If the talon card was moved then enabled the top-most card so that the next card can be played
// TODO Can this be self-contained??
if(talonCard != null && !cards.contains(talonCard)) {
CardView lastTalonCard = viewFactory.get(TalonPileView.class).getLastCard();
if(lastTalonCard != null) {
lastTalonCard.setEnabled(true);
}
}
}
}
================================================
FILE: src/game/views/helpers/WinAnimationHelper.java
================================================
package game.views.helpers;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import framework.core.factories.AbstractFactory;
import framework.core.factories.ViewFactory;
import framework.core.system.Application;
import game.config.OptionsPreferences;
import game.views.CardView;
import game.views.FoundationPileView;
import game.views.GameView;
import game.views.StatusBarView;
/**
* This helper class performs a win animation on the specified card
*
* @author Daniel Ricci {@literal }
*/
public class WinAnimationHelper {
/**
* The timer used to update the position of the cards associated to the foundations
*/
private static Timer _timer;
/**
* The queue of foundation views, ordered in priority of rendering importance
*/
private static final Queue _foundations = new LinkedList();
/**
* The key adapter that handles when a key is pressed during the animation phase
*/
private static final KeyAdapter _keyAdapter = new KeyAdapter() {
@Override public void keyPressed(KeyEvent event) {
clear();
GameView.showGameOverDialog();
}
};
/**
* The mouse adapter that handles when a mouse button is pressed during the animation phase
*/
private static final MouseAdapter _mouseAdapter = new MouseAdapter() {
@Override public void mousePressed(MouseEvent event) {
clear();
GameView.showGameOverDialog();
}
};
/**
* The component adapter that handles when the window changes size during the animation phase
* which causes the layout manager to destroy the layout of the game
*/
private static final ComponentAdapter _componentAdapter = new ComponentAdapter() {
@Override public void componentResized(ComponentEvent event) {
clear();
GameView.showGameOverDialog();
}
};
/**
* The canvas width
*/
private static int _canvasWidth;
/**
* The canvas height
*/
private static int _canvasHeight;
/**
* The card view that is being manipulated
*/
private final CardView _cardView;
/**
* The x-position being used for the card coordinate
*/
private double _x;
/**
* The y-position being used for the card coordinate
*/
private double _y;
/**
* The change in `x` over time
*/
private double _deltaX = Math.floor(Math.random() * 6 - 3) * 2;
/*
* The change in `y` over time
*/
private double _deltaY = -Math.random() * 16;
/**
* Constructs a new instance of this class type
*
* @param cardView The card view to animate
*/
private WinAnimationHelper(CardView cardView) {
_cardView = cardView;
Point position = cardView.getParentIView().getContainerClass().getLocation();
_x = position.getX();
_y = position.getY();
if(_deltaX == 0) {
_deltaX = 1;
}
}
/**
* Process all the cards held by the foundation views
*/
public static void processCards() {
// Get the list of foundation piles
List foundationsList = AbstractFactory.getFactory(ViewFactory.class).getAll(FoundationPileView.class);
// Reverse the list so that we start with the left-most pile.
Collections.reverse(foundationsList);
// Initialize this helper class
initialize();
// Populate the queue of items to be processed
_foundations.addAll(foundationsList);
}
/**
* Initializes this helper in preparation for rendering the cards associated to the foundations
*/
private static void initialize() {
OptionsPreferences preferences = new OptionsPreferences();
preferences.load();
GameView gameView = AbstractFactory.getFactory(ViewFactory.class).get(GameView.class);
_canvasWidth = gameView.getWidth();
_canvasHeight = gameView.getHeight() - (preferences.statusBar ? AbstractFactory.getFactory(ViewFactory.class).get(StatusBarView.class).getHeight() : 0);
// Clear this class before proceeding
clear();
Application.instance.getJMenuBar().addMouseListener(_mouseAdapter);
for(int i = 0; i < Application.instance.getJMenuBar().getMenuCount(); ++i) {
Application.instance.getJMenuBar().getMenu(i).setEnabled(false);
}
// Add the listeners for detecting whenever a click or a mouse event occurs during
// the animation so that the animation will stop and the dialog for starting a new game will get prompted
gameView.addMouseListener(_mouseAdapter);
Application.instance.addKeyListener(_keyAdapter);
Application.instance.addComponentListener(_componentAdapter);
_timer = new Timer(true);
_timer.schedule(new TimerTask() {
WinAnimationHelper helper = null;
boolean hadValues = false;
@Override public void run() {
if(_foundations.size() > 0) {
hadValues = true;
if(helper != null) {
if(!helper.update()) {
helper._cardView.getParent().remove(helper._cardView);
helper = null;
}
}
else {
// Get a reference to the current head of the foundations list
FoundationPileView foundation = _foundations.poll();
// If the foundation exists then remove it from the list and get the
// last card. Provided that it exists then create a helper object to
// animate the card and put the foundation at the back of the queue
if(foundation != null) {
_foundations.remove(foundation);
CardView card = foundation.getLastCard();
if(card != null) {
helper = new WinAnimationHelper(card);
_foundations.add(foundation);
}
}
}
}
else {
if(hadValues) {
clear();
GameView.showGameOverDialog();
}
}
}
}, 0, 1000/80);
}
/**
* Performs an update by performing both a next step point calculation and a draw routine
*
* @return TRUE if the operation was successful, false otherwise
*/
private boolean update() {
Point point = calculateNextStep();
if(point == null) {
return false;
}
draw(point);
return true;
}
/**
* Draws the currently set card view to the specified position
*
* @param point The position to draw to
*/
private void draw(Point point) {
CardView cardView = CardView.createLightWeightCard(_cardView);
cardView.render();
ViewFactory viewFactory = AbstractFactory.getFactory(ViewFactory.class);
GameView gameView = viewFactory.get(GameView.class);
gameView.add(cardView, 0);
cardView.setBounds(new Rectangle(point.x, point.y, _cardView.getWidth(), _cardView.getHeight()));
}
/**
* Calculates the next position that the currently set card will be at
*
* @return The position associated to the next step where the card would be at
*/
private Point calculateNextStep() {
// Take the change in X and the change in Y and apply them respectively
_x += _deltaX;
_y += _deltaY;
// If you are outside the left or right canvas limits then the card should not
// longer be positioned anywhere relevant so do not return any position
if(_x < -CardView.CARD_WIDTH || _x > _canvasWidth) {
return null;
}
// If the position is outside canvas height (with respect to the bottom of the card)
if(_y > _canvasHeight - CardView.CARD_HEIGHT) {
// Normalize the position of the card by placing it on the theoretical bottom of the canvas
_y = _canvasHeight - CardView.CARD_HEIGHT;
// Take the change in `y` inverse it, this along will cause the card to bounce upwards
// Take only a small percentage of the delta so that it bounces less
_deltaY = -_deltaY * 0.85;
}
_deltaY += 0.98;
return new Point((int)_x, (int)_y);
}
/**
* Clears the contents of this helper
*/
public static void clear() {
if(_timer != null) {
_timer.cancel();
_timer = null;
}
ViewFactory viewFactory = AbstractFactory.getFactory(ViewFactory.class);
if(viewFactory != null) {
GameView gameView = viewFactory.get(GameView.class);
if(gameView != null) {
gameView.removeMouseListener(_mouseAdapter);
}
}
Application.instance.removeKeyListener(_keyAdapter);
Application.instance.getJMenuBar().removeMouseListener(_mouseAdapter);
Application.instance.removeComponentListener(_componentAdapter);
// Enable back all the menu items
// TODO - Can this can actually be put within the MenuBuilder and then just be called from there?
for(int i = 0; i < Application.instance.getJMenuBar().getMenuCount(); ++i) {
Application.instance.getJMenuBar().getMenu(i).setEnabled(true);
}
_foundations.clear();
}
}