From d5688dfd092275a7a8d7290fc892df220a0919de Mon Sep 17 00:00:00 2001 From: mico Date: Tue, 21 Apr 2020 13:25:50 +0300 Subject: [PATCH 01/16] + settings --- .vscode/settings.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..01586d6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.exclude": { + "**/.classpath": true, + "**/.project": true, + "**/.settings": true, + "**/.factorypath": true + } +} \ No newline at end of file From cf6197b2522dc39ffd478b872f05e87731631f83 Mon Sep 17 00:00:00 2001 From: mico Date: Tue, 21 Apr 2020 13:26:29 +0300 Subject: [PATCH 02/16] + freecell basis --- freecell/FreecellBasis.java | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 freecell/FreecellBasis.java diff --git a/freecell/FreecellBasis.java b/freecell/FreecellBasis.java new file mode 100644 index 0000000..332aaf3 --- /dev/null +++ b/freecell/FreecellBasis.java @@ -0,0 +1,50 @@ +package freecell; + +public class FreecellBasis { + public final int + DESK_SIZE, + PILE_START, PILE_NUM, PILE_END, + BASE_START, BASE_NUM, BASE_END, + CELL_START, CELL_NUM, CELL_END; + + FreecellBasis( + final int PILE_NUM, // cascades + final int CELL_NUM, // open cells + final int BASE_NUM // foundation piles + ) { + this.PILE_NUM = PILE_NUM; + this.CELL_NUM = CELL_NUM; + this.BASE_NUM = BASE_NUM; + this.DESK_SIZE = PILE_NUM + CELL_NUM + BASE_NUM; + + this.PILE_START = 0; + this.PILE_END = this.BASE_START = this.PILE_START + this.PILE_NUM; + this.BASE_END = this.CELL_START = this.BASE_START + this.BASE_NUM; + this.CELL_END = this.CELL_START + this.CELL_NUM; + } + + boolean isPile(final int index) { + return index >= this.PILE_START && index < this.PILE_END; + } + + boolean isBase(final int index) { + return index >= this.BASE_START && index < this.BASE_END; + } + + boolean isCell(final int index) { + return index >= this.CELL_START && index < this.CELL_END; + } + + String getSpotName(final int index) { + if (this.isBase(index)) { + return "base " + (index - this.BASE_START); + } + if (this.isPile(index)) { + return "pile " + (index - this.PILE_START); + } + if (this.isCell(index)) { + return "cell " + (index - this.CELL_START); + } + return "unknown " + index; + } +} From 95878328149ecf413cc0b197c2f2f0121ce77b48 Mon Sep 17 00:00:00 2001 From: mico Date: Tue, 21 Apr 2020 13:36:23 +0300 Subject: [PATCH 03/16] + main --- freecell/FreecellBasis.java | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/freecell/FreecellBasis.java b/freecell/FreecellBasis.java index 332aaf3..a7cde73 100644 --- a/freecell/FreecellBasis.java +++ b/freecell/FreecellBasis.java @@ -8,9 +8,9 @@ public class FreecellBasis { CELL_START, CELL_NUM, CELL_END; FreecellBasis( - final int PILE_NUM, // cascades - final int CELL_NUM, // open cells - final int BASE_NUM // foundation piles + final int PILE_NUM, // cascades + final int CELL_NUM, // open cells + final int BASE_NUM // foundation piles ) { this.PILE_NUM = PILE_NUM; this.CELL_NUM = CELL_NUM; @@ -47,4 +47,18 @@ String getSpotName(final int index) { } return "unknown " + index; } + + public static void main(String[] args) { + FreecellBasis basis = new FreecellBasis(8, 4, 4); + + System.out.println("Basis Test"); + System.out.println("DESK_SIZE: " + basis.DESK_SIZE); + System.out.println("BASE_NUM: " + basis.BASE_NUM); + System.out.println("CELL_NUM: " + basis.CELL_NUM); + System.out.println("PILE_NUM: " + basis.PILE_NUM); + + for (int i = 0; i < basis.DESK_SIZE; i++) { + System.out.println("SPOT #" + i + "\n\t" + basis.getSpotName(i)); + } + } } From 7b76391904a4573f6b3b1c1f0cdb6c3ef941ea64 Mon Sep 17 00:00:00 2001 From: mico Date: Tue, 21 Apr 2020 14:05:05 +0300 Subject: [PATCH 04/16] port from typescript --- common/Deck.java | 162 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 common/Deck.java diff --git a/common/Deck.java b/common/Deck.java new file mode 100644 index 0000000..7db87ba --- /dev/null +++ b/common/Deck.java @@ -0,0 +1,162 @@ +package common; + +/** + * Standard 52-card deck + */ + +public class Deck { + public static final String SUITS = "SDCH"; + public static final char SUIT_CHARACTERS[] = {'S', 'D', 'C', 'H'}; + public static final char SUIT_PLAY_NAMES[] = {'♠', '♦', '♣', '♥'}; + + public static final String SUIT_FULL_NAMES[] = { + "spades", + "diamonds", + "clubs", + "hearts" + }; + + public static final String SUIT_HTML_CODES[] = { + "♠", + "♦", + "♣", + "♥" + }; // Special Symbol Character Codes for HTML + + public static final String RANKS = "A23456789TJQK"; + public static final char RANK_CHARACTERS[] = { 'A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K' }; + + public static final String RANK_PLAY_NAMES[] = { + "A", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "J", + "Q", + "K" + }; + + public static final String RANK_FULL_NAMES[] = { + "Ace", + "Two", + "Three", + "Four", + "Five", + "Six", + "Seven", + "Eight", + "Nine", + "Ten", + "Jack", + "Queen", + "King" + }; + + public static final int SUIT_NUM = SUITS.length(); + public static final int RANK_NUM = RANKS.length(); + + // Standard 52-card deck + public static final int CARD_NUM = SUIT_NUM * RANK_NUM; + + // Card index is defined as: suit + rank * SUIT_NUM + public static int indexOf(int s, int r) { + return s + r * SUIT_NUM; + } + + public static int rankOf(int index) { + return index / SUIT_NUM; + } + + public static int suitOf(int index) { + return index % SUIT_NUM; + } + + // Card names: + public static String nameOf(int index) { + return "" + RANKS.charAt(rankOf(index)) + SUITS.charAt(suitOf(index)); + } + + public static String playNameOf(int index) { + return RANK_PLAY_NAMES[rankOf(index)] + SUIT_PLAY_NAMES[suitOf(index)]; + } + + public static String fullNameOf(int index) { + return ( + RANK_FULL_NAMES[rankOf(index)] + " of " + SUIT_FULL_NAMES[suitOf(index)] + ); + } + + // Suit names: + public static char suitNameOf(int index) { + return SUITS.charAt(suitOf(index)); + } + + public static char suitPlayNameOf(int index) { + return SUIT_PLAY_NAMES[suitOf(index)]; + } + + public static String suitFullNameOf(int index) { + return SUIT_FULL_NAMES[suitOf(index)]; + } + + public static String suitHTMLCodeOf(int index) { + return SUIT_HTML_CODES[suitOf(index)]; + } + + // Rank names: + public static char rankNameOf(int index) { + return RANKS.charAt(rankOf(index)); + } + + public static String rankPlayNameOf(int index) { + return RANK_PLAY_NAMES[rankOf(index)]; + } + + public static String rankFullNameOf(int index) { + return RANK_FULL_NAMES[rankOf(index)]; + } + + // A set of optionally shuffled playing cards. + int[] deck(int seed) { + int cards[] = new int[CARD_NUM]; + for (int i = 0; i < CARD_NUM; i++) { + cards[i] = i; + } + + if (seed >= 0) { + // use LCG algorithm to pick up cards from the deck + // http://en.wikipedia.org/wiki/Linear_congruential_generator + final int m = 0x80000000; + final int a = 1103515245; + final int c = 12345; + + for (int i = 0; i < CARD_NUM; i++) { + seed = (a * seed + c) % m; + + // swap cards + final int j = seed % CARD_NUM; + if (i != j) { + final int card = cards[i]; + cards[i] = cards[j]; + cards[j] = card; + } + } + } + return cards; + } + + public static boolean isTableau(int cardA, int cardB) { + return ( + // rankOf(cardA) === (rankOf(cardB) + 1) % RANK_NUM && + rankOf(cardA) == rankOf(cardB) + 1 && + suitOf(cardA) % 2 != suitOf(cardB) % 2 + ); + } + +} \ No newline at end of file From 48a68d65ec8baccd7d1a8fdcb20e0033160c19f8 Mon Sep 17 00:00:00 2001 From: mico Date: Tue, 21 Apr 2020 14:12:53 +0300 Subject: [PATCH 05/16] + main --- common/Deck.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/Deck.java b/common/Deck.java index 7db87ba..b938b96 100644 --- a/common/Deck.java +++ b/common/Deck.java @@ -123,7 +123,7 @@ public static String rankFullNameOf(int index) { } // A set of optionally shuffled playing cards. - int[] deck(int seed) { + public static int[] deck(int seed) { int cards[] = new int[CARD_NUM]; for (int i = 0; i < CARD_NUM; i++) { cards[i] = i; @@ -159,4 +159,13 @@ public static boolean isTableau(int cardA, int cardB) { ); } + public static void main(String[] args) { + System.out.println("Deck Test"); + + final int cards[] = deck(-1); + for (int card : cards) { + System.out.println("\t" + card + "\t" + nameOf(card) + "\t" + fullNameOf(card)); + } + } + } \ No newline at end of file From 4011dd2d675c3e0a58f734c21c7fe2a39ce6efe3 Mon Sep 17 00:00:00 2001 From: mico Date: Sat, 25 Apr 2020 14:21:06 +0300 Subject: [PATCH 06/16] + freecell solver prototype --- common/Deck.java | 21 ++- common/IntStack.java | 116 ++++++++++++++ freecell/FreecellBasis.java | 30 ++-- freecell/FreecellDesk.java | 191 ++++++++++++++++++++++ freecell/FreecellGame.java | 299 ++++++++++++++++++++++++++++++++++ freecell/FreecellSolver.java | 300 +++++++++++++++++++++++++++++++++++ game-list.csv | 25 +++ test.txt | 128 +++++++++++++++ 8 files changed, 1094 insertions(+), 16 deletions(-) create mode 100644 common/IntStack.java create mode 100644 freecell/FreecellDesk.java create mode 100644 freecell/FreecellGame.java create mode 100644 freecell/FreecellSolver.java create mode 100644 game-list.csv create mode 100644 test.txt diff --git a/common/Deck.java b/common/Deck.java index b938b96..2e3d04e 100644 --- a/common/Deck.java +++ b/common/Deck.java @@ -82,6 +82,11 @@ public static String nameOf(int index) { return "" + RANKS.charAt(rankOf(index)) + SUITS.charAt(suitOf(index)); } + public static void appendNameOf(StringBuilder buf, int index) { + buf.append(RANKS.charAt(rankOf(index))); + buf.append(SUITS.charAt(suitOf(index))); + } + public static String playNameOf(int index) { return RANK_PLAY_NAMES[rankOf(index)] + SUIT_PLAY_NAMES[suitOf(index)]; } @@ -123,24 +128,26 @@ public static String rankFullNameOf(int index) { } // A set of optionally shuffled playing cards. - public static int[] deck(int seed) { + public static int[] deck(long seed) { int cards[] = new int[CARD_NUM]; for (int i = 0; i < CARD_NUM; i++) { cards[i] = i; } if (seed >= 0) { + // System.out.println("SEED: " + seed); // use LCG algorithm to pick up cards from the deck // http://en.wikipedia.org/wiki/Linear_congruential_generator - final int m = 0x80000000; - final int a = 1103515245; - final int c = 12345; + final double m = 0x80000000L; + final double a = 1103515245L; + final double c = 12345L; for (int i = 0; i < CARD_NUM; i++) { - seed = (a * seed + c) % m; + seed = (long) ((a * seed + c) % m); // swap cards - final int j = seed % CARD_NUM; + final int j = (int)(seed % CARD_NUM); + // System.out.println("" + i + " " + j + " " + seed); if (i != j) { final int card = cards[i]; cards[i] = cards[j]; @@ -162,7 +169,7 @@ public static boolean isTableau(int cardA, int cardB) { public static void main(String[] args) { System.out.println("Deck Test"); - final int cards[] = deck(-1); + final int cards[] = deck(1); for (int card : cards) { System.out.println("\t" + card + "\t" + nameOf(card) + "\t" + fullNameOf(card)); } diff --git a/common/IntStack.java b/common/IntStack.java new file mode 100644 index 0000000..1515e0f --- /dev/null +++ b/common/IntStack.java @@ -0,0 +1,116 @@ +package common; + +import java.util.Arrays; + +public final class IntStack { + private int _size; + private int _available; + private int _data[]; + + public IntStack(final int data[], final int size) { + _data = data; + _available = _data.length; + _size = Math.min(Math.max(size, 0), _available); + } + + public IntStack(final int data[]) { + this(data, 0); + } + + /** + * Creates an empty Stack. + */ + public IntStack() { + this(new int[16]); + } + + /** + * Tests if this stack is empty. + * + * @return {@code true} if the stack is empty; {@code false} otherwise. + */ + public boolean isEmpty() { + return _size == 0; + } + + /** + * Looks at the top of this stack without removing it from the stack. + * + * @return the top of the stack + */ + public int peek() { + return _data[_size - 1]; + } + + /** + * Removes the top of this stack and returns it as the value of this function. + * + * @return the top of the stack + */ + public int pop() { + return _data[--_size]; + } + + /** + * Pushes an item onto the top of this stack. + * + * @param value an item to push. + */ + public void push(final int value) { + if (_size == _available) { + _available *= 2; + _data = Arrays.copyOf(_data, _available); + } + _data[_size++] = value; + } + + /** + * Removes all of the elements from the stack. It will be empty after this call returns. + */ + public void clear() { + _size = 0; + } + + /** + * Returns the element at the specified position in the stack. + * @param index - index of the element to return + * @return the element at the specified index + */ + public int get(final int index) { + return _data[index]; + } + + /** + * Returns an array containing all of the elements in this stack. + * @return an array + */ + public int[] toArray​() { + return Arrays.copyOf(_data, _size); + } + + public int size() { + return _size; + } + + public int available() { + return _available; + } + + /** + * Returns a string representation of this stack. + * @return a string + */ + @Override + public String toString() { + final var buf = new StringBuilder(); + var prefix = ""; + buf.append('['); + for (int i = 0; i < _size; i++) { + buf.append(prefix); + buf.append(Integer.toString(_data[i])); + prefix = ", "; + } + buf.append(']'); + return buf.toString(); + } +} \ No newline at end of file diff --git a/freecell/FreecellBasis.java b/freecell/FreecellBasis.java index a7cde73..0f66431 100644 --- a/freecell/FreecellBasis.java +++ b/freecell/FreecellBasis.java @@ -24,26 +24,38 @@ public class FreecellBasis { } boolean isPile(final int index) { - return index >= this.PILE_START && index < this.PILE_END; + return index >= PILE_START && index < PILE_END; } boolean isBase(final int index) { - return index >= this.BASE_START && index < this.BASE_END; + return index >= BASE_START && index < BASE_END; } boolean isCell(final int index) { - return index >= this.CELL_START && index < this.CELL_END; + return index >= CELL_START && index < CELL_END; + } + + int toMove(final int giver, final int taker) { + return giver + taker * DESK_SIZE; + } + + int toGiver(final int move) { + return move % DESK_SIZE; + } + + int toTaker(final int move) { + return move / DESK_SIZE; } String getSpotName(final int index) { - if (this.isBase(index)) { - return "base " + (index - this.BASE_START); + if (isBase(index)) { + return "base " + (index - BASE_START); } - if (this.isPile(index)) { - return "pile " + (index - this.PILE_START); + if (isPile(index)) { + return "pile " + (index - PILE_START); } - if (this.isCell(index)) { - return "cell " + (index - this.CELL_START); + if (isCell(index)) { + return "cell " + (index - CELL_START); } return "unknown " + index; } diff --git a/freecell/FreecellDesk.java b/freecell/FreecellDesk.java new file mode 100644 index 0000000..73146c8 --- /dev/null +++ b/freecell/FreecellDesk.java @@ -0,0 +1,191 @@ +package freecell; + +import java.util.Arrays; +// import java.util.ArrayList; +// import java.util.Collections; + +import common.Deck; +import common.IntStack; + +public class FreecellDesk extends FreecellBasis { + protected final IntStack _desk[]; + + public FreecellDesk(final int PILE_NUM, final int CELL_NUM, final int BASE_NUM) { + super(PILE_NUM, CELL_NUM, BASE_NUM); + + this._desk = new IntStack[DESK_SIZE]; + for (int i = 0; i < DESK_SIZE; i++) { + this._desk[i] = new IntStack(); + } + } + + /** + * Returns a copy of the desk. + */ + // public int[][] toArray() { + // final int arr[][] = new int[DESK_SIZE][]; + // for (int i = 0; i < DESK_SIZE; i++) { + // arr[i] = this._desk[i].toArray​(); + // } + + // return arr; + // } + + /** + * Gets a card at [index, offset] + * + * @param index a line index + * @param offset an offset in the line. A negative value can be used, indicating + * an offset from the end of the sequence. + */ + // public int getCard(final int index, int offset) { + // final var line = this._desk[index]; + // final int size = line.size(); + + // if (offset < 0) { + // offset = size + offset; + // } + // return offset >= 0 && offset < size ? line.get(offset) : -1; + // } + + // public List getLine(final int index) { + // return Collections.unmodifiableList(this.desk[index]); + // } + + // public int[] getTableauAt(final int index) { + // final var tableau = new ArrayList(); + // final var line = this._desk[index]; + + // int j = line.size(); + // if (j > 0) { + // tableau.add(line.get(j - 1)); + // while (--j > 0 && Deck.isTableau(line.get(j - 1), line.get(j)) && Deck.rankOf(line.get(j - 1)) > 0) { + // tableau.add(line.get(j - 1)); + // } + // } + // Collections.reverse(tableau); + // return tableau.stream().mapToInt(i -> i).toArray(); + // } + + public int countEmptyCells() { + int count = 0; + for (int i = this.CELL_START; i < this.CELL_END; i++) { + if (this._desk[i].size() == 0) { + count++; + } + } + return count; + } + + public int countEmptyPiles() { + int count = 0; + for (int i = this.PILE_START; i < this.PILE_END; i++) { + if (this._desk[i].size() == 0) { + count++; + } + } + return count; + } + + public boolean isSolved() { + for (int i = this.BASE_START; i < this.BASE_END; i++) { + if (this._desk[i].size() < Deck.RANK_NUM) { + return false; + } + } + return true; + } + + public int countSolved() { + int count = 0; + for (int i = BASE_START; i < BASE_END; i++) { + count += _desk[i].size(); + } + return count; + } + + public int countEmpty() { + return this.countEmptyCells() + this.countEmptyPiles(); + } + + private static final String RANKS = "_" + Deck.RANKS; + + public String baseToKey() { + final var buf = new StringBuilder(); + for (int i = BASE_START; i < BASE_END; i++) { + buf.append(RANKS.charAt(_desk[i].size())); + } + return buf.toString(); + } + + public String pileToKey() { + final var arr = new String[this.PILE_NUM]; + for (int i = 0; i < this.PILE_NUM; i++) { + arr[i] = lineToString(this._desk[i + this.PILE_START]); + } + Arrays.sort(arr); + return String.join(",", arr); + } + + public String toKey() { + return this.baseToKey() + this.pileToKey(); + } + + int getEmptyCell() { + for (int i = this.CELL_START; i < this.CELL_END; i++) { + if (this._desk[i].size() == 0) { + return i; + } + } + return -1; + } + + int getEmptyPile() { + for (int i = this.PILE_START; i < this.PILE_END; i++) { + if (this._desk[i].size() == 0) { + return i; + } + } + return -1; + } + + int getBase(final int card) { + final int suit = Deck.suitOf(card); + final int rank = Deck.rankOf(card); + + for (int i = BASE_START + suit; i < BASE_END; i += Deck.SUIT_NUM) { + if (_desk[i].size() == rank) { + return i; + } + } + return -1; + } + + int getOppositeColorBaseMinRank(final int suit) { + int rank = Deck.RANK_NUM; + for (int i = BASE_START + ((suit + 1) % 2); i < BASE_END; i += Deck.SUIT_NUM / 2) { + rank = Math.min(rank, _desk[i].size()); + } + return rank; + } + + @Override + public String toString() { + final var buf = new StringBuilder(); + var prefix = ""; + for (int i = 0; i < DESK_SIZE; i++) { + buf.append(prefix); + buf.append(lineToString(_desk[i])); + prefix = ","; + } + return buf.toString(); + } + + public static String lineToString(final IntStack line) { + final var buf = new StringBuilder(); + for (int i = 0; i < line.size(); i++) { + Deck.appendNameOf(buf, line.get(i)); + } + return buf.toString(); + } +} diff --git a/freecell/FreecellGame.java b/freecell/FreecellGame.java new file mode 100644 index 0000000..c5d8ea8 --- /dev/null +++ b/freecell/FreecellGame.java @@ -0,0 +1,299 @@ +package freecell; + +import common.Deck; +import common.IntStack; + +public class FreecellGame extends FreecellDesk { + // public interface OnMove { + // void test(FreecellGame game); + // } + protected IntStack _path = new IntStack(); + + public FreecellGame(final int PILE_NUM, final int CELL_NUM, final int BASE_NUM) { + super(PILE_NUM, CELL_NUM, BASE_NUM); + } + + /** + * Clears the game. + */ + public void clear() { + for (int i = DESK_SIZE; i-- > 0;) { + _desk[i].clear(); + } + _path.clear(); + } + + // protected void _addCard(final int destination, final int card) { + // _desk[destination].push(card); + // } + + /** + * Pops the last card from the `source` line and add it to the `destination` + * + * @param giver source line + * @param taker destination line + */ + public void moveCard(final int giver, final int taker) { + _path.push(toMove(giver, taker)); + _desk[taker].push(_desk[giver].pop()); + } + + public void moveCard(final int move) { + _path.push(move); + _desk[toTaker(move)].push(_desk[toGiver(move)].pop()); + } + + public void forward(final int[] path) { + for (final int move : path) { + _path.push(move); + // move source => destination + _desk[toTaker(move)].push(_desk[toGiver(move)].pop()); + } + } + + public void backward(final int mark) { + while (_path.size() > mark) { + final int move = _path.pop(); + // move destination => source + _desk[toGiver(move)].push(_desk[toTaker(move)].pop()); + } + } + + // public void moveCard(final int move) { + // moveCard(toGiver(move), toTaker(move)); + // } + + public boolean isMoveForward(final int move) { + return _path.isEmpty() || _path.peek() != toMove(toTaker(move), toGiver(move)); + } + + public void moveCardsToBases() { + for (boolean next = true; next;) { + next = false; + for (int giver = 0; giver < DESK_SIZE; giver++) { + if (!(isBase(giver) || _desk[giver].isEmpty())) { + final int taker = getBase(_desk[giver].peek()); + if (taker >= 0) { + moveCard(giver, taker); + next = true; + } + } + } + } + } + + public void moveCardsAuto() { + for (boolean next = true; next;) { + next = false; + for (int giver = 0; giver < DESK_SIZE; giver++) { + if (!(isBase(giver) || _desk[giver].isEmpty())) { + final int card = _desk[giver].peek(); + final int taker = getBase(card); + if (taker >= 0) { + if (Deck.rankOf(card) <= getOppositeColorBaseMinRank(Deck.suitOf(card)) + 1) { + moveCard(giver, taker); + next = true; + } + } + } + } + } + } + + // private void _onMove(final OnMove onMove, final int giver, final int taker) { + // // Ignore reverse moves + // if (_path.isEmpty() || toTaker(_path.peek()) != giver || toGiver(_path.peek()) != taker) { + // final int mark = _path.size(); + // moveCard(giver, taker); + // // moveCardsAuto(); + // onMove.test(this); + // backward(mark); + // } + // } + + public int getBaseMinRank() { + int rank = _desk[BASE_START].size(); + for (int i = 1; i < BASE_NUM; i++) { + rank = Math.min(rank, _desk[BASE_START + i].size()); + } + return rank; + } + + // public void findMoves(final OnMove onMove) { + // // First make all mandatory moves to the bases. + // int ranks[] = { _desk[BASE_START].size(), _desk[BASE_START + 1].size() }; + // for (int i = 2; i < BASE_NUM; i++) { + // ranks[i & 1] = Math.min(ranks[i & 1], _desk[BASE_START + i].size()); + // } + + // for (int giver = 0; giver < DESK_SIZE; giver++) { + // if (!(_desk[giver].isEmpty() || isBase(giver))) { + // final int card = _desk[giver].peek(); + // final int suit = Deck.suitOf(card); + // final int rank = Deck.rankOf(card); + // if (rank <= ranks[(suit + 1) & 1] + 1) { + // final int base = getBase(card); + // if (base >= 0) { + // _onMove(onMove, giver, base); + // return; // Only one mangatory move is allowed. + // } + // } + // } + // } + + // final int emptyCell = getEmptyCell(); + // final int emptyPile = getEmptyPile(); + + // for (int giver = 0; giver < DESK_SIZE; giver++) { + // if (!_desk[giver].isEmpty()) { + // final int card = _desk[giver].peek(); + // final int suit = Deck.suitOf(card); + // final int rank = Deck.rankOf(card); + + // if (isBase(giver)) { + // // We can take cards from bases only to form a tableau. + // if (rank > ranks[(suit + 1) & 1] + 1) { + // for (int pile = PILE_START; pile < PILE_END; pile++) { + // if (!_desk[pile].isEmpty() && Deck.isTableau(_desk[pile].peek(), card)) { + // _onMove(onMove, giver, pile); + // } + // } + // } + // } else { + // // Cells and piles: + // // 1. To the base. + // for (int base = BASE_START + suit; base < BASE_END; base += Deck.SUIT_NUM) { + // if (_desk[base].size() == rank) { + // _onMove(onMove, giver, base); + // break; // one base is enough. + // } + // } + // // 2. To a tableau. + // for (int pile = PILE_START; pile < PILE_END; pile++) { + // if (!_desk[pile].isEmpty() && Deck.isTableau(_desk[pile].peek(), card)) { + // _onMove(onMove, giver, pile); + // } + // } + + // if (isCell(giver)) { + // // Cells only + // // 3. To an empty pile. + // if (emptyPile >= 0) { + // _onMove(onMove, giver, emptyPile); + // } + // } else { + // // It should be a pile then. + // // 3. To an empty cell. + // if (emptyCell >= 0) { + // _onMove(onMove, giver, emptyCell); + // } + // // 4. To an empty pile. + // if (emptyPile >= 0 && _desk[giver].size() > 1) { + // _onMove(onMove, giver, emptyPile); + // } + // } + // } + // } + // } + // } + + public void getMoves(final IntStack moves) { + moves.clear(); + + // Get opposite color bases minimal ranks. + int ranks[] = { _desk[BASE_START].size(), _desk[BASE_START + 1].size() }; + for (int i = 2; i < BASE_NUM; i++) { + ranks[i & 1] = Math.min(ranks[i & 1], _desk[BASE_START + i].size()); + } + + final int emptyCell = getEmptyCell(); + final int emptyPile = getEmptyPile(); + + for (int giver = DESK_SIZE; giver-- > 0;) { + if (!_desk[giver].isEmpty()) { + final int card = _desk[giver].peek(); + final int suit = Deck.suitOf(card); + final int rank = Deck.rankOf(card); + + if (isBase(giver)) { + // We can take cards from bases only to form a tableau. + if (rank > ranks[(suit + 1) & 1] + 1) { + for (int pile = PILE_START; pile < PILE_END; pile++) { + if (!_desk[pile].isEmpty() && Deck.isTableau(_desk[pile].peek(), card)) { + moves.push(toMove(giver, pile)); + } + } + } + } else { + if (isCell(giver)) { + // Cells only + // 1. To an empty pile. + if (emptyPile >= 0) { + moves.push(toMove(giver, emptyPile)); + } + } else { + // It should be a pile then. + // 1. To an empty cell. + if (emptyCell >= 0) { + moves.push(toMove(giver, emptyCell)); + } + // 2. To an empty pile. + if (emptyPile >= 0 && _desk[giver].size() > 1) { + moves.push(toMove(giver, emptyPile)); + } + } + + // Cells and piles: + // 1. To a tableau. + for (int pile = PILE_START; pile < PILE_END; pile++) { + if (!_desk[pile].isEmpty() && Deck.isTableau(_desk[pile].peek(), card)) { + moves.push(toMove(giver, pile)); + } + } + // 2. To the base. + for (int base = BASE_START + suit; base < BASE_END; base += Deck.SUIT_NUM) { + if (_desk[base].size() == rank) { + if (rank <= ranks[(suit + 1) & 1] + 1) { + // It's a mandatory move to the base. Clear all other moves and return this move only. + moves.clear(); + moves.push(toMove(giver, base)); + return; + } else { + moves.push(toMove(giver, base)); + break; // one base is enough. + } + } + } + } + } + } + } + + public void rewind() { + backward(0); + } + + // public int pathLength() { + // return _path.size(); + // } + + // public int[] pathToArray() { + // return _path.toArray​(); + // } + + /** + * Makes a new deal. + * + * @param seed seed number + */ + int[] deal(final int seed) { + final var cards = Deck.deck(seed); + + clear(); + for (int i = 0; i < cards.length; i++) { + _desk[PILE_START + (i % PILE_NUM)].push(cards[i]); + } + return cards; + } + +} \ No newline at end of file diff --git a/freecell/FreecellSolver.java b/freecell/FreecellSolver.java new file mode 100644 index 0000000..48553de --- /dev/null +++ b/freecell/FreecellSolver.java @@ -0,0 +1,300 @@ +package freecell; + +import java.util.ArrayList; +import java.util.HashMap; +// import java.util.HashSet; +import java.util.List; +import java.util.Map; +// import java.util.Set; + +import common.Deck; +import common.IntStack; + +public class FreecellSolver extends FreecellGame { + public FreecellSolver(int PILE_NUM, int CELL_NUM, int BASE_NUM) { + super(PILE_NUM, CELL_NUM, BASE_NUM); + } + + // private final Set _done = new HashSet<>(); + private Map _output = new HashMap<>(); + private final List> _stack = new ArrayList<>(); + + private int _iteration = 0; + private int _solution[]; + + // private static class SolvedException extends RuntimeException { + // /** + // * Serial UID + // */ + // private static final long serialVersionUID = 1L; + + // public SolvedException() { + // super(); + // } + // } + + private static class DeskState implements Comparable { + public final int free, solved; + public final String key; + + public DeskState(String key, int free, int solved) { + this.key = key; + this.free = free; + this.solved = solved; + } + + @Override + public int compareTo(DeskState other) { + if (solved != other.solved) { + return other.solved - solved; + } + if (free != other.free) { + return other.free - free; + } + return key.compareTo(other.key); + } + } + + // private final FreecellGame.OnMove _onMove = new FreecellGame.OnMove() { + // @Override + // public void test(FreecellGame game) { + // final var key = game.toKey(); + // if (!_shouldSolve()) { + // _done.remove(key); + // } else { + // if (!_done.contains(key)) { + // _done.add(key); + // _output.put(key, game.pathToArray()); + + // game.moveCardsToBases(); + // if (game.isSolved()) { + // if (_solution == null || _solution.length > game.pathLength()) { + // _solution = game.pathToArray(); + // System.out.println("*********\nSolution: " + _solution.length); + // System.out.println("Cleaning output: " + _output.size() + "\n*********\n"); + // _done.removeAll(_output.keySet()); + // _output.clear(); + // } + // } + // } + // } + // } + // }; + + public void prepare() { + // _done.clear(); + _stack.clear(); + _output.clear(); + _iteration = 0; + + rewind(); + moveCardsAuto(); + final var key = toKey(); + // _done.add(key); + _output.put(key, _path.toArray​()); + } + + private Map _getNextInput() { + final int INPUT_MAX = 50000; + if (_output.size() > 0) { + return _splitInput(_output, INPUT_MAX, INPUT_MAX / 10); + } + + if (_stack.size() > 0) { + // System.out.println("Step back:" + _stack.size()); + // return _splitInput(_stack.remove(_stack.size() - 1), INPUT_MAX, INPUT_MAX / 3); + return _stack.remove(_stack.size() - 1); + } + + return null; + } + + private Map _splitInput(Map input, int inputMax, int splitSize) { + final int inputSize = input.size(); + if (inputSize <= inputMax) { + return input; + } + + var list = new ArrayList(inputSize); + // int countEmptyMax = 0; + // int countSolvedMax = 0; + for (final var key : input.keySet()) { + rewind(); + forward(input.get(key)); + final int countEmpty = countEmpty(); + final int countSolved = countSolved(); + + // countEmptyMax = Math.max(countEmptyMax, countEmpty); + // countSolvedMax = Math.max(countSolvedMax, countSolved); + + list.add(new DeskState(key, countEmpty, countSolved)); + } + + // var a = new ArrayList(); + // var b = new ArrayList(); + // for (int i = 0; i < inputSize; i++) { + // final var state = list.get(i); + // if (state.free == countEmptyMax || state.solved == countSolvedMax) { + // a.add(input.get(state.index)); + // } else { + // b.add(input.get(state.index)); + // } + // } + // System.out.println("\tSplitting input: " + input.size() + " => " + splitSize + " + " + (inputSize - splitSize)); + list.sort(null); + + while (input.size() > splitSize) { + final var buf = new HashMap(splitSize); + while (buf.size() < splitSize && input.size() > splitSize) { + final var key = list.get(input.size() - 1).key; + buf.put(key, input.remove(key)); + } + _stack.add(buf); + } + + // for (int i = 0; i < inputSize - splitSize; i++) { + // int index = list.get(i).index; + // b.add(input.get(index)); + // } + + + + // System.out.println("\tPath Length: " + input.get(0).length); + return input; + } + + private final boolean _shouldSolve(int pathLength) { + if (_solution != null) { + var cards = Deck.CARD_NUM - countSolved(); + return _solution.length > pathLength + cards; + } + return true; + } + + // private boolean _removeIfLong(String key, int pathLength) { + // if (_solution != null) { + // if (pathLength + (Deck.CARD_NUM - countSolved()) >= _solution.length) { + // // _done.remove(key); + // return true; + // } + // } + // return false; + // } + + public boolean nextIteration() { + var input = _getNextInput(); + if (_output.size() > 0) { + _output = new HashMap(); + } + + if (input == null) { + return false; + } + + final int inputSize = input.size(); + _iteration++; + if (_iteration > 5000) { + return false; + } + + if (inputSize <= 0) { + return false; + } + + /* + System.out.println("#" + _iteration + + ": input=" + inputSize + // + ", done=" + _done.size() + + ", stack=" + _stack.size()); + */ + System.out.print('-'); + if (_iteration % 100 == 0) { + System.out.println("\n" + (_iteration / 100) + ":[" + _stack.size() + "]"); + } + + final IntStack moves = new IntStack(); + // for (final var entry : input.entrySet()) { + for (final var path : input.values()) { + // final var path = entry.getValue(); + final int mark = path.length; + rewind(); + forward(path); + // if (!_removeIfLong(entry.getKey(), mark + 1)) { + if (_shouldSolve(mark + 1)) { + getMoves(moves); + while (moves.size() > 0) { + int move = moves.pop(); + if (isMoveForward(move)) { + moveCard(move); + final var key = toKey(); + // if (!_removeIfLong(key, mark + 1)) { + if (_shouldSolve(mark + 1)) { + // if (!_done.contains(key)) { + if (!input.containsKey(key) && !_output.containsKey(key)) { + // _done.add(key); + _output.put(key, _path.toArray​()); + + moveCardsToBases(); + if (isSolved()) { + if (_solution == null || _solution.length > _path.size()) { + _solution = _path.toArray​(); + System.out.println("\n*********\nSolution: " + + _solution.length + + '\n' + pathToString(_solution) + + "\n*********\n"); + } + } + } + } + backward(mark); + } + } + } + } + + return _output.size() > 0 || _stack.size() > 0; + } + + public int[] solve() { + prepare(); + while (nextIteration()); + return _solution; + } + + public static char toChar(int n) { + if (n >= 0 && n < 10) { + return (char)('0' + n); + } else { + return (char)('a' + n - 10); + } + } + + public String pathToString(int[] path) { + var buf = new StringBuilder(path.length); + for (var move : path) { + buf.append(toChar(toGiver(move))); + buf.append(toChar(toTaker(move))); + } + return buf.toString(); + } + + public static void main(String[] args) { + final int deal = 25; + var game = new FreecellSolver(8, 4, 4); + game.deal(deal); + System.out.println("DESK: " + game.toString()); + System.out.println("KEY: " + game.toKey()); + + var path = game.solve(); + if (path != null) { + System.out.println("Solved!"); + System.out.println("" + deal + + ',' + path.length + + ',' + game.pathToString(path)); + game.rewind(); + game.forward(path); + } else { + System.out.println("Unsolved ;-("); + } + } +} diff --git a/game-list.csv b/game-list.csv new file mode 100644 index 0000000..eda7a9e --- /dev/null +++ b/game-list.csv @@ -0,0 +1,25 @@ +deal,mark,path +1,81,6c6d606e6b3656161f18101beb1e18e14e4048c83c32314b5b31393a4af17f737b5b707a79191a0a5aca272cdb2a272d29190928180879090a7828c9395158d80c0d0b6b18fb010a6a091a6bc9daeb4968 +2,83,486c646d696e6bc47674143c10e616193e3f39c95c50e05e59697909c9353c3a1a23292a3a7a7b2b245beb2e2878c8580c021318684858010845d8054d49090a0a4849474b5acadae8fb1a4a6b7a5b2b4b0b3b +3,86,594c5424202d24297e7b7854575a29576235636b151065161f1912187179f97f707a3739323a3b3818da4a616d68f84f4808d8490d292802c90958067a4645494b0c04090bfb0a4b1b4a6adb2a5a6bea2b5bcb28385a +4,80,3c3d3b563e505f5a5b0b3beb3e39c3f313181b010c0bdb2b2b632d6bcb2c2f2aca040c1a040a606a68f07f7aca754a753a7379474a4c4b4a1419ca091828495908485868e8384959d9187939f9386839 +5,83,287c707d7e474bc067346c3f39696b69e91e195919492924f414242b1b18183868512628525b58587f75287238783828023212f50103070f02050a6acada4a2a4b2b4a2a4b2b4a5b1a3b5aeb0b4a4a49f95979 +6,85,4c4d4e404f4af4d4643d343fe35e535a7464fa676f636b101614171b195958787939f93f213929252b2a5bfa36387858090f757b78782807090b4b791937380309070a39586a284a6bfa184beb4ada4b7b4a78ca4b +7,87,1c1d5414543e101a1b18da3431313a387d7071c37c7a0f0a705ac01c1a5127c12c2a535425d524282962636d6b4b0bdb48080b2b056d690709f9491948181b4939483849397b7838ebd8094b4aca4b5a0b5909584c4aca +8,76,38631c6d1e19c9636956565c59585b1b242509e5391e1a2f292a35707a31303a6a4a5afa595a7f7b3b09750a7909fb0f0908e8386b4b4e47206168f918c81bdb18384beb284a084b580b0a7a +9,90,6c6d4e6f696bc92cd646262436262b37c35716171b5c575a2a1a6a3d303b4a6bfb47e24e6a4f48f41f1818565831353868737818717458757274527a7579f978c808070939690b1b49596a294a6b4b6adb2a0a0929e90a280b7b +10,87,303c3d3151515e5f5b3b3a51757b7818eb0e45057bf7454b4f6424f46f6bdb6deb6e68f838c3232023297919161c1f1b1519c9391c1a187a6adae878fa3a5a01260609395949030a5a4a5b6b0b5a0949394a48c8182868 +11,90,35151c1d171e1b1f31d132373d38d36d62676b5b53c32b5b502c2b626a5b565aea6aca7a46241a2c2b140b0e0a01cb0c080aea2e0af82fca7c1a202971757672727939e93878392938c928495918485868f80849d94b694b494b +12,79,38676c6d6e6b0b2024c02c2f2b3b3810673b12e61e1b19565b51593919f93f3afa5ada7d7b767f7a7929c9437c78474349f9434f4aea0a6a0b0a6b09081818d8285838293b6838c8010b7b0a1afa09 +13,78,1b5c5d5e515f5ad51d15d5121d1b1a5b2a2b5a5bcbeb7c454b7b3b1e6b4b4148047534357531c1313839e8f8787969636a24583a2a2829d909595a0b0308680a18283a6809285949194a1a49194a +14,76,3c3d5e305fc03c30323a52535b5975787b7a0acb1c0b3b131b2bcb2c2bdb7a5b450a1a1519656a696d69292a4a0939490309050b0aea203a2e2328183878f8e8c808586809594958d84a5b49 +15,101,0c0d320209523e394f4942d4f46d6f62f2626f68f81f76141016195909f97f795070d070785d575b1b183135382838232858256be535230e0b271537212321256526f62f2afaea5a0a5b0b5a0a5b5a4a7a280e0b5b4b7beb4a404b4969196878d8091a39ca +16,76,4a1c18626b6d61646836345e34545f5159393a3976797a1a2a131a194956151b242b26292b3b4b4a2428d87276783848496869fb484d434b68eb58db0b0a4b0b050a280a18ca096939496a5a +17,102,2c242d2e707421285453c4545a1c5158756525e57e727a1f17f71f13141a1b2b7b7a62646b0768061803f6060f0171070929c9f94939d930393231393c384850583d385258e86943684e4fd84d49694545494aca4c4b680b3b0a2beafb1b2a5bda1a5a7a1bcb +18,83,70737b7c7a072d572e272a2f2bdb3d323638424a483bd40d0b0ad01d141b3a1aeb1e181b5b31396a65624bd26d6368691939c8e8f901030b09d9595c565d5879d8c868484b450b4a2a7a29792a781a29395a28 +19,83,4c4020424d405e40545f545059794929e56ef96925293fd93d39d9c925192c2a1d151a141b6b5a4b4a5b5a3ada7a747b5b3538386818716368587808020d0405060b380ada0a5a0a0b2beb09284bfb18486bc8 +20,83,3c3d404a3e3a63606a6b67601f161a2a2bc25c5ae25e5b5956252a2620d02d2925d5323d38181b181959085809087138d87d030a7a797b5b39692b680969d90902010d73494a4a0a4a0b1bfb1878cb2b38dbe8 +21,81,4a7c50757d7e7f7a37171a371318381a17d72a2d2ada2d2beb3bdb5d5a586b18315e6134395929f1616f69e96838280204030e09d979020b1b187819c9184cf84841094958194b2b4a7bcb2a7a4a7beb3a +22,80,2c2d2e23536525292f1218f8726fe26e68696b58d8767bf60d0f091619e13079395e5659e65e32345b5835363a3878e8587a1a386a0a1b6b0b186a2a6b296a2a4a690a2949c90a4b0a484b1b4bdbfb49 +23,72,287a2c232d2e2bdb297d127279e9121e121a5a7a6a636f6bd9fb5d5f5b2b3beb3e103b38583a1868582a68e8783e393808e80e380b4149f94a29d92a4b4a1b09c9e9010a4b090a1a +24,79,092c2a282b292a1216101d1ad13d31303a313e38e37343737a7b424e4b1b0f6b6a0602030b530ac05c5b586acb5c2b626861691959187819f9183918293ac82a3978d93a4a68394b2b0b2838eb0a09 \ No newline at end of file diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..e68a9e6 --- /dev/null +++ b/test.txt @@ -0,0 +1,128 @@ +DESK: KDKC7C6H9C9HAD,8S7S3C6D8C5HJH,QS2C2DAHASACQH,2SKS4H4C7D5SJD,THQC3HKHTCJC,5D9S3S7H8H8D,JS3D4D4S9D6C,TS6S2H5CTDQD,,,,,,,, +KEY: ____2SKS4H4C7D5SJD,5D9S3S7H8H8D,8S7S3C6D8C5HJH,JS3D4D4S9D6C,KDKC7C6H9C9HAD,QS2C2DAHASACQH,THQC3HKHTCJC,TS6S2H5CTDQD +--------------------------------------------------------- +********* +Solution: 82 +091c16101d2e2a282b292a1ad132313d3a31e3437e737a7b424f4b1b6b6a0602030b530ae05e5beb2b4e4b346acb3a3c303b62fb3f3858685361691959187819d9184918293878c92a680a29ea0928f80a09 +********* + +- +********* +Solution: 81 +091c16101d2e2a282b292a1ad132313d3a31e3437e737a7b424f4b1b6b6a0602030b530ae05e5beb2b4e4bcb3c6a3a342a3aea3b3e38586368546169fb1959187819d918c9183948784968290928e80a09 +********* + +------------------------------------------ +1:[300] +---------------------------------------------------------------------------------------------------- +2:[281] +---------------------------------------------------------------------------------------------------- +3:[270] +---------------------------------------------------------------------------------------------------- +4:[264] +--- +********* +Solution: 80 +092c2a282b292a1216101d1ad13d31303a313e38e37343737a7b424e4b1b0f6b6a0602030b530ac0d46a5c5b58cb6c685d612b691959187819f91839c9183ad82a3949783a4a68394b2b0b2838eb0a09 +********* + +------------------------------------------------------------------------------------------------- +5:[238] +---------------------------------------------------------------------------------------------------- +6:[224] +---------------------------------------------------------------------------------------------------- +7:[231] +-------------------------------------------------------------- +********* +Solution: 79 +092c2a282b292a1216101d1ad13d31303a313e38e37343737a7b424e4b1b0f6b6a0602030b530ac05c5b586acb5c2b626861691959187819f9183918293ac82a3978d93a4a68394b2b0b2838eb0a09 +********* + +-------------------------------------- +8:[226] +---------------------------------------------------------------------------------------------------- +9:[208] +---------------------------------------------------------------------------------------------------- +10:[239] +---------------------------------------------------------------------------------------------------- +11:[219] +---------------------------------------------------------------------------------------------------- +12:[203] +---------------------------------------------------------------------------------------------------- +13:[214] +---------------------------------------------------------------------------------------------------- +14:[205] +---------------------------------------------------------------------------------------------------- +15:[244] +---------------------------------------------------------------------------------------------------- +16:[222] +---------------------------------------------------------------------------------------------------- +17:[208] +---------------------------------------------------------------------------------------------------- +18:[227] +---------------------------------------------------------------------------------------------------- +19:[203] +---------------------------------------------------------------------------------------------------- +20:[206] +---------------------------------------------------------------------------------------------------- +21:[215] +---------------------------------------------------------------------------------------------------- +22:[194] +---------------------------------------------------------------------------------------------------- +23:[200] +---------------------------------------------------------------------------------------------------- +24:[213] +---------------------------------------------------------------------------------------------------- +25:[198] +---------------------------------------------------------------------------------------------------- +26:[225] +---------------------------------------------------------------------------------------------------- +27:[232] +---------------------------------------------------------------------------------------------------- +28:[211] +---------------------------------------------------------------------------------------------------- +29:[199] +---------------------------------------------------------------------------------------------------- +30:[197] +---------------------------------------------------------------------------------------------------- +31:[195] +---------------------------------------------------------------------------------------------------- +32:[195] +---------------------------------------------------------------------------------------------------- +33:[195] +---------------------------------------------------------------------------------------------------- +34:[218] +---------------------------------------------------------------------------------------------------- +35:[207] +---------------------------------------------------------------------------------------------------- +36:[191] +---------------------------------------------------------------------------------------------------- +37:[208] +---------------------------------------------------------------------------------------------------- +38:[184] +---------------------------------------------------------------------------------------------------- +39:[185] +---------------------------------------------------------------------------------------------------- +40:[174] +---------------------------------------------------------------------------------------------------- +41:[220] +---------------------------------------------------------------------------------------------------- +42:[202] +---------------------------------------------------------------------------------------------------- +43:[215] +---------------------------------------------------------------------------------------------------- +44:[210] +---------------------------------------------------------------------------------------------------- +45:[194] +---------------------------------------------------------------------------------------------------- +46:[206] +---------------------------------------------------------------------------------------------------- +47:[206] +---------------------------------------------------------------------------------------------------- +48:[195] +---------------------------------------------------------------------------------------------------- +49:[189] +---------------------------------------------------------------------------------------------------- +50:[272] +Solved! +24,79,092c2a282b292a1216101d1ad13d31303a313e38e37343737a7b424e4b1b0f6b6a0602030b530ac05c5b586acb5c2b626861691959187819f9183918293ac82a3978d93a4a68394b2b0b2838eb0a09 From ec483c7b5c08c56062b57728eb07147a24525262 Mon Sep 17 00:00:00 2001 From: mico Date: Sat, 25 Apr 2020 14:22:01 +0300 Subject: [PATCH 07/16] + deal #25 --- game-list.csv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/game-list.csv b/game-list.csv index eda7a9e..c3dbdd2 100644 --- a/game-list.csv +++ b/game-list.csv @@ -22,4 +22,5 @@ deal,mark,path 21,81,4a7c50757d7e7f7a37171a371318381a17d72a2d2ada2d2beb3bdb5d5a586b18315e6134395929f1616f69e96838280204030e09d979020b1b187819c9184cf84841094958194b2b4a7bcb2a7a4a7beb3a 22,80,2c2d2e23536525292f1218f8726fe26e68696b58d8767bf60d0f091619e13079395e5659e65e32345b5835363a3878e8587a1a386a0a1b6b0b186a2a6b296a2a4a690a2949c90a4b0a484b1b4bdbfb49 23,72,287a2c232d2e2bdb297d127279e9121e121a5a7a6a636f6bd9fb5d5f5b2b3beb3e103b38583a1868582a68e8783e393808e80e380b4149f94a29d92a4b4a1b09c9e9010a4b090a1a -24,79,092c2a282b292a1216101d1ad13d31303a313e38e37343737a7b424e4b1b0f6b6a0602030b530ac05c5b586acb5c2b626861691959187819f9183918293ac82a3978d93a4a68394b2b0b2838eb0a09 \ No newline at end of file +24,79,092c2a282b292a1216101d1ad13d31303a313e38e37343737a7b424e4b1b0f6b6a0602030b530ac05c5b586acb5c2b626861691959187819f9183918293ac82a3978d93a4a68394b2b0b2838eb0a09 +25,78,404c32343234383d3a531373101a1b532a5e585beb2b181e18212b7f7b717a2029792919491a4a61676a69685849790aca4c454b3b0b0a045b051b01270928d83868f8394959e8093a78ca0b1b0a \ No newline at end of file From 7cb4437b8316f1cc9d48a6bdb487b1b80b222b4c Mon Sep 17 00:00:00 2001 From: mico Date: Sun, 3 May 2020 16:50:30 +0300 Subject: [PATCH 08/16] freecell solver v2 --- common/ByteStack.java | 150 +++++++++++ common/Deck.java | 183 +++++-------- freecell/FreecellDesk.java | 135 ++++------ freecell/FreecellGame.java | 485 ++++++++++++++++++++++------------- freecell/FreecellSolver.java | 344 ++++++++++--------------- 5 files changed, 701 insertions(+), 596 deletions(-) create mode 100644 common/ByteStack.java diff --git a/common/ByteStack.java b/common/ByteStack.java new file mode 100644 index 0000000..753113e --- /dev/null +++ b/common/ByteStack.java @@ -0,0 +1,150 @@ +package common; + +import java.util.Arrays; + +public final class ByteStack implements Comparable { + private int _size; + private int _available; + private byte _data[]; + + public ByteStack(final byte data[], final int size) { + _data = data; + _available = _data.length; + _size = Math.min(Math.max(size, 0), _available); + } + + public ByteStack(final byte data[]) { + this(data, 0); + } + + /** + * Creates an empty Stack. + */ + public ByteStack() { + this(new byte[64]); + } + + /** + * Tests if this stack is empty. + * + * @return {@code true} if the stack is empty; {@code false} otherwise. + */ + public boolean isEmpty() { + return _size == 0; + } + + /** + * Looks at the top of this stack without removing it from the stack. + * + * @return the top of the stack + */ + public byte peek() { + return _data[_size - 1]; + } + + /** + * Removes the top of this stack and returns it as the value of this function. + * + * @return the top of the stack + */ + public byte pop() { + return _data[--_size]; + } + + /** + * Pushes an item onto the top of this stack. + * + * @param value an item to push. + */ + public void push(final byte value) { + if (_size == _available) { + _available *= 2; + _data = Arrays.copyOf(_data, _available); + } + _data[_size++] = value; + } + + public void push(final ByteStack values) { + final int S = values._size; + if (_size + S > _available) { + _available += Math.max(_available, S); + _data = Arrays.copyOf(_data, _available); + } + final byte D[] = values._data; + for (int i = 0; i < S; i++) { + _data[_size++] = D[i]; + } + } + + /** + * Removes all of the elements from the stack. It will be empty after this call returns. + */ + public void clear() { + _size = 0; + } + + /** + * Returns the element at the specified position in the stack. + * @param index - index of the element to return + * @return the element at the specified index + */ + public byte get(final int index) { + return _data[index]; + } + + /** + * Returns an array containing all of the elements in this stack. + * @return an array + */ + public byte[] toArray​() { + return Arrays.copyOf(_data, _size); + } + + public int size() { + return _size; + } + + public int available() { + return _available; + } + + /** + * Returns a string representation of this stack. + * @return a string + */ + @Override + public String toString() { + return new String(_data, 0, _size); + } + + @Override + public int compareTo(ByteStack other) { + // Since: 9 + // int cmp = Byte.compare(_data, 0, _size, other._data, 0, other._size); + if (_size != other._size) { + return _size - other._size; + } + for (int i = 0; i < _size; i++) { + if (_data[i] != other._data[i]) { + return _data[i] - other._data[i]; + } + } + return 0; + } + + public static void main(String[] args) { + var stack = new ByteStack(); + for (byte b = 32; b < 127; b++) { + stack.push(b); + } + + System.out.println("ASCII Printables: " + stack); + + var reversal = new ByteStack(); + while (stack.size() > 0) { + reversal.push(stack.pop()); + } + + System.out.println("In opposite direction: " + reversal); + } +} diff --git a/common/Deck.java b/common/Deck.java index 2e3d04e..9eaa208 100644 --- a/common/Deck.java +++ b/common/Deck.java @@ -1,137 +1,63 @@ package common; -/** - * Standard 52-card deck - */ - public class Deck { - public static final String SUITS = "SDCH"; - public static final char SUIT_CHARACTERS[] = {'S', 'D', 'C', 'H'}; - public static final char SUIT_PLAY_NAMES[] = {'♠', '♦', '♣', '♥'}; - - public static final String SUIT_FULL_NAMES[] = { - "spades", - "diamonds", - "clubs", - "hearts" - }; - - public static final String SUIT_HTML_CODES[] = { - "♠", - "♦", - "♣", - "♥" - }; // Special Symbol Character Codes for HTML - - public static final String RANKS = "A23456789TJQK"; - public static final char RANK_CHARACTERS[] = { 'A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K' }; - - public static final String RANK_PLAY_NAMES[] = { - "A", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "J", - "Q", - "K" - }; - - public static final String RANK_FULL_NAMES[] = { - "Ace", - "Two", - "Three", - "Four", - "Five", - "Six", - "Seven", - "Eight", - "Nine", - "Ten", - "Jack", - "Queen", - "King" - }; - - public static final int SUIT_NUM = SUITS.length(); - public static final int RANK_NUM = RANKS.length(); - // Standard 52-card deck - public static final int CARD_NUM = SUIT_NUM * RANK_NUM; + public static final byte + CARD_NUM = 52, // SUIT_NUM * RANK_NUM + SUIT_NUM = 4, + RANK_NUM = 13; - // Card index is defined as: suit + rank * SUIT_NUM - public static int indexOf(int s, int r) { - return s + r * SUIT_NUM; - } + public static final char + SUITS[] = { 'S', 'D', 'C', 'H' }, + RANKS[] = { 'A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K' }; - public static int rankOf(int index) { - return index / SUIT_NUM; - } - - public static int suitOf(int index) { - return index % SUIT_NUM; - } - - // Card names: - public static String nameOf(int index) { - return "" + RANKS.charAt(rankOf(index)) + SUITS.charAt(suitOf(index)); - } - - public static void appendNameOf(StringBuilder buf, int index) { - buf.append(RANKS.charAt(rankOf(index))); - buf.append(SUITS.charAt(suitOf(index))); - } + public static final int + COLOR_POS = 5, + COLOR_FLAG = 1 << COLOR_POS, + RANK_MASK = COLOR_FLAG - 1; - public static String playNameOf(int index) { - return RANK_PLAY_NAMES[rankOf(index)] + SUIT_PLAY_NAMES[suitOf(index)]; + public static byte cardOf(int index) { + return (byte)('A' + ((index >>> 1) | ((index & 1) << COLOR_POS))); } - public static String fullNameOf(int index) { - return ( - RANK_FULL_NAMES[rankOf(index)] + " of " + SUIT_FULL_NAMES[suitOf(index)] - ); + // Returns 0 for blacks (spades and clubs) and 1 for reds (diamonds and hearts) + public static int colorOf(byte card) { + return ((card & COLOR_FLAG) >>> COLOR_POS); } - // Suit names: - public static char suitNameOf(int index) { - return SUITS.charAt(suitOf(index)); + public static boolean isBlack(byte card) { + return colorOf(card) == 0; } - public static char suitPlayNameOf(int index) { - return SUIT_PLAY_NAMES[suitOf(index)]; + public static boolean isRed(byte card) { + return !isBlack(card); } - public static String suitFullNameOf(int index) { - return SUIT_FULL_NAMES[suitOf(index)]; - } - - public static String suitHTMLCodeOf(int index) { - return SUIT_HTML_CODES[suitOf(index)]; + // Card index is defined as: suit + rank * SUIT_NUM + public static int indexOf(int s, int r) { + return s + r * SUIT_NUM; } - // Rank names: - public static char rankNameOf(int index) { - return RANKS.charAt(rankOf(index)); + // Returns the rank of a card. + // Cards are ranked, from 0 to 12: A, 2, 3, 4, 5, 6, 7, 8, 9, T, J, Q and K. + public static int rankOf(byte card) { + return (((card - 1) & RANK_MASK) >>> 1); } - public static String rankPlayNameOf(int index) { - return RANK_PLAY_NAMES[rankOf(index)]; + // Returns the suit of a card. The order of suits: Spades, Diamonds, Clubs and Hearts. + public static int suitOf(byte card) { + return (((card - 1) & 1) << 1) | colorOf(card); } - public static String rankFullNameOf(int index) { - return RANK_FULL_NAMES[rankOf(index)]; + public static boolean isTableau(byte cardA, byte cardB) { + return (rankOf(cardA) == rankOf(cardB) + 1 && colorOf(cardA) != colorOf(cardB)); } - // A set of optionally shuffled playing cards. - public static int[] deck(long seed) { - int cards[] = new int[CARD_NUM]; + // Returns a set of optionally shuffled playing cards. + public static byte[] deal(long seed) { + byte cards[] = new byte[CARD_NUM]; for (int i = 0; i < CARD_NUM; i++) { - cards[i] = i; + cards[i] = cardOf(i); } if (seed >= 0) { @@ -147,32 +73,39 @@ public static int[] deck(long seed) { // swap cards final int j = (int)(seed % CARD_NUM); - // System.out.println("" + i + " " + j + " " + seed); if (i != j) { - final int card = cards[i]; + final byte card = cards[i]; cards[i] = cards[j]; cards[j] = card; } } } + return cards; } - public static boolean isTableau(int cardA, int cardB) { - return ( - // rankOf(cardA) === (rankOf(cardB) + 1) % RANK_NUM && - rankOf(cardA) == rankOf(cardB) + 1 && - suitOf(cardA) % 2 != suitOf(cardB) % 2 - ); - } + public static String toString(byte card) { + int suit = suitOf(card); + int rank = rankOf(card); + boolean black = isBlack(card); - public static void main(String[] args) { - System.out.println("Deck Test"); + return "Card #" + indexOf(suit, rank) + " " + RANKS[rank] + SUITS[suit] + (black ? " black" : " red"); + } - final int cards[] = deck(1); - for (int card : cards) { - System.out.println("\t" + card + "\t" + nameOf(card) + "\t" + fullNameOf(card)); + public static String toString(byte[] cards) { + var buf = new StringBuilder(); + for (byte card : cards) { + buf.append(toString(card)).append('\n'); } + return buf.toString(); } -} \ No newline at end of file + public static void main(String[] args) { + var cards = deal(-1); + System.out.println(toString(cards)); + + var str = new String(cards); + System.out.println("As string:"); + System.out.println(str); + } +} diff --git a/freecell/FreecellDesk.java b/freecell/FreecellDesk.java index 73146c8..4f5f069 100644 --- a/freecell/FreecellDesk.java +++ b/freecell/FreecellDesk.java @@ -1,76 +1,29 @@ package freecell; import java.util.Arrays; -// import java.util.ArrayList; -// import java.util.Collections; +import common.ByteStack; import common.Deck; -import common.IntStack; public class FreecellDesk extends FreecellBasis { - protected final IntStack _desk[]; + private ByteStack _key = new ByteStack(); + private final ByteStack _buffer[]; + protected final ByteStack _desk[]; public FreecellDesk(final int PILE_NUM, final int CELL_NUM, final int BASE_NUM) { super(PILE_NUM, CELL_NUM, BASE_NUM); - this._desk = new IntStack[DESK_SIZE]; + _buffer = new ByteStack[PILE_NUM]; + _desk = new ByteStack[DESK_SIZE]; for (int i = 0; i < DESK_SIZE; i++) { - this._desk[i] = new IntStack(); + _desk[i] = new ByteStack(); } } - /** - * Returns a copy of the desk. - */ - // public int[][] toArray() { - // final int arr[][] = new int[DESK_SIZE][]; - // for (int i = 0; i < DESK_SIZE; i++) { - // arr[i] = this._desk[i].toArray​(); - // } - - // return arr; - // } - - /** - * Gets a card at [index, offset] - * - * @param index a line index - * @param offset an offset in the line. A negative value can be used, indicating - * an offset from the end of the sequence. - */ - // public int getCard(final int index, int offset) { - // final var line = this._desk[index]; - // final int size = line.size(); - - // if (offset < 0) { - // offset = size + offset; - // } - // return offset >= 0 && offset < size ? line.get(offset) : -1; - // } - - // public List getLine(final int index) { - // return Collections.unmodifiableList(this.desk[index]); - // } - - // public int[] getTableauAt(final int index) { - // final var tableau = new ArrayList(); - // final var line = this._desk[index]; - - // int j = line.size(); - // if (j > 0) { - // tableau.add(line.get(j - 1)); - // while (--j > 0 && Deck.isTableau(line.get(j - 1), line.get(j)) && Deck.rankOf(line.get(j - 1)) > 0) { - // tableau.add(line.get(j - 1)); - // } - // } - // Collections.reverse(tableau); - // return tableau.stream().mapToInt(i -> i).toArray(); - // } - public int countEmptyCells() { int count = 0; - for (int i = this.CELL_START; i < this.CELL_END; i++) { - if (this._desk[i].size() == 0) { + for (int i = CELL_START; i < CELL_END; i++) { + if (_desk[i].isEmpty()) { count++; } } @@ -79,8 +32,8 @@ public int countEmptyCells() { public int countEmptyPiles() { int count = 0; - for (int i = this.PILE_START; i < this.PILE_END; i++) { - if (this._desk[i].size() == 0) { + for (int i = PILE_START; i < PILE_END; i++) { + if (_desk[i].isEmpty()) { count++; } } @@ -88,8 +41,8 @@ public int countEmptyPiles() { } public boolean isSolved() { - for (int i = this.BASE_START; i < this.BASE_END; i++) { - if (this._desk[i].size() < Deck.RANK_NUM) { + for (int i = BASE_START; i < BASE_END; i++) { + if (_desk[i].size() < Deck.RANK_NUM) { return false; } } @@ -105,35 +58,48 @@ public int countSolved() { } public int countEmpty() { - return this.countEmptyCells() + this.countEmptyPiles(); + return countEmptyCells() + countEmptyPiles(); } - private static final String RANKS = "_" + Deck.RANKS; + private static final byte BASES[] = { '_', 'A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K' }; - public String baseToKey() { - final var buf = new StringBuilder(); + public void baseToKey(ByteStack key) { for (int i = BASE_START; i < BASE_END; i++) { - buf.append(RANKS.charAt(_desk[i].size())); + key.push(BASES[_desk[i].size()]); } - return buf.toString(); } - public String pileToKey() { - final var arr = new String[this.PILE_NUM]; - for (int i = 0; i < this.PILE_NUM; i++) { - arr[i] = lineToString(this._desk[i + this.PILE_START]); + public void pileToKey(ByteStack key) { + int size = 0; + for (int i = PILE_START; i < PILE_END; i++) { + if (!_desk[i].isEmpty()) { + _buffer[size++] = _desk[i]; + } + } + if (size > 0) { + Arrays.sort(_buffer, 0, size); + for (int i = 0; i < size; i++) { + key.push(_buffer[i]); + key.push((byte)','); + } + key.pop(); } - Arrays.sort(arr); - return String.join(",", arr); + } + + public void toKey(ByteStack key) { + key.clear(); + baseToKey(key); + pileToKey(key); } public String toKey() { - return this.baseToKey() + this.pileToKey(); + toKey(_key); + return _key.toString(); } int getEmptyCell() { - for (int i = this.CELL_START; i < this.CELL_END; i++) { - if (this._desk[i].size() == 0) { + for (int i = CELL_START; i < CELL_END; i++) { + if (_desk[i].isEmpty()) { return i; } } @@ -141,15 +107,15 @@ int getEmptyCell() { } int getEmptyPile() { - for (int i = this.PILE_START; i < this.PILE_END; i++) { - if (this._desk[i].size() == 0) { + for (int i = PILE_START; i < PILE_END; i++) { + if (_desk[i].isEmpty()) { return i; } } return -1; } - int getBase(final int card) { + int getBase(final byte card) { final int suit = Deck.suitOf(card); final int rank = Deck.rankOf(card); @@ -161,14 +127,6 @@ int getBase(final int card) { return -1; } - int getOppositeColorBaseMinRank(final int suit) { - int rank = Deck.RANK_NUM; - for (int i = BASE_START + ((suit + 1) % 2); i < BASE_END; i += Deck.SUIT_NUM / 2) { - rank = Math.min(rank, _desk[i].size()); - } - return rank; - } - @Override public String toString() { final var buf = new StringBuilder(); @@ -181,10 +139,11 @@ public String toString() { return buf.toString(); } - public static String lineToString(final IntStack line) { + public static String lineToString(final ByteStack line) { final var buf = new StringBuilder(); for (int i = 0; i < line.size(); i++) { - Deck.appendNameOf(buf, line.get(i)); + buf.append(Deck.RANKS[Deck.rankOf(line.get(i))]); + buf.append(Deck.SUITS[Deck.suitOf(line.get(i))]); } return buf.toString(); } diff --git a/freecell/FreecellGame.java b/freecell/FreecellGame.java index c5d8ea8..1e8cbdc 100644 --- a/freecell/FreecellGame.java +++ b/freecell/FreecellGame.java @@ -4,9 +4,6 @@ import common.IntStack; public class FreecellGame extends FreecellDesk { - // public interface OnMove { - // void test(FreecellGame game); - // } protected IntStack _path = new IntStack(); public FreecellGame(final int PILE_NUM, final int CELL_NUM, final int BASE_NUM) { @@ -23,10 +20,6 @@ public void clear() { _path.clear(); } - // protected void _addCard(final int destination, final int card) { - // _desk[destination].push(card); - // } - /** * Pops the last card from the `source` line and add it to the `destination` * @@ -59,58 +52,47 @@ public void backward(final int mark) { } } - // public void moveCard(final int move) { - // moveCard(toGiver(move), toTaker(move)); - // } - public boolean isMoveForward(final int move) { return _path.isEmpty() || _path.peek() != toMove(toTaker(move), toGiver(move)); } - public void moveCardsToBases() { + public int moveCardsToBases() { + int count = 0; for (boolean next = true; next;) { next = false; for (int giver = 0; giver < DESK_SIZE; giver++) { - if (!(isBase(giver) || _desk[giver].isEmpty())) { - final int taker = getBase(_desk[giver].peek()); - if (taker >= 0) { - moveCard(giver, taker); - next = true; - } + if (isBase(giver) || _desk[giver].isEmpty()) { + continue; + } + final int taker = getBase(_desk[giver].peek()); + if (taker >= 0) { + moveCard(giver, taker); + count++; + next = true; } } } + return count; } - public void moveCardsAuto() { - for (boolean next = true; next;) { - next = false; - for (int giver = 0; giver < DESK_SIZE; giver++) { - if (!(isBase(giver) || _desk[giver].isEmpty())) { - final int card = _desk[giver].peek(); - final int taker = getBase(card); - if (taker >= 0) { - if (Deck.rankOf(card) <= getOppositeColorBaseMinRank(Deck.suitOf(card)) + 1) { - moveCard(giver, taker); - next = true; - } - } + public int moveCardsAuto() { + final int[] ranks = getBaseMinRanks(); + for (int giver = 0; giver < DESK_SIZE; giver++) { + if (isBase(giver) || _desk[giver].isEmpty()) { + continue; + } + final byte card = _desk[giver].peek(); + if (Deck.rankOf(card) <= ranks[Deck.colorOf(card)] + 1) { + final int taker = getBase(card); + if (taker >= 0 ) { + moveCard(giver, taker); + return 1 + moveCardsAuto(); } } } + return 0; } - // private void _onMove(final OnMove onMove, final int giver, final int taker) { - // // Ignore reverse moves - // if (_path.isEmpty() || toTaker(_path.peek()) != giver || toGiver(_path.peek()) != taker) { - // final int mark = _path.size(); - // moveCard(giver, taker); - // // moveCardsAuto(); - // onMove.test(this); - // backward(mark); - // } - // } - public int getBaseMinRank() { int rank = _desk[BASE_START].size(); for (int i = 1; i < BASE_NUM; i++) { @@ -119,148 +101,262 @@ public int getBaseMinRank() { return rank; } - // public void findMoves(final OnMove onMove) { - // // First make all mandatory moves to the bases. - // int ranks[] = { _desk[BASE_START].size(), _desk[BASE_START + 1].size() }; - // for (int i = 2; i < BASE_NUM; i++) { - // ranks[i & 1] = Math.min(ranks[i & 1], _desk[BASE_START + i].size()); - // } - - // for (int giver = 0; giver < DESK_SIZE; giver++) { - // if (!(_desk[giver].isEmpty() || isBase(giver))) { - // final int card = _desk[giver].peek(); - // final int suit = Deck.suitOf(card); - // final int rank = Deck.rankOf(card); - // if (rank <= ranks[(suit + 1) & 1] + 1) { - // final int base = getBase(card); - // if (base >= 0) { - // _onMove(onMove, giver, base); - // return; // Only one mangatory move is allowed. - // } - // } - // } - // } - - // final int emptyCell = getEmptyCell(); - // final int emptyPile = getEmptyPile(); - - // for (int giver = 0; giver < DESK_SIZE; giver++) { - // if (!_desk[giver].isEmpty()) { - // final int card = _desk[giver].peek(); - // final int suit = Deck.suitOf(card); - // final int rank = Deck.rankOf(card); - - // if (isBase(giver)) { - // // We can take cards from bases only to form a tableau. - // if (rank > ranks[(suit + 1) & 1] + 1) { - // for (int pile = PILE_START; pile < PILE_END; pile++) { - // if (!_desk[pile].isEmpty() && Deck.isTableau(_desk[pile].peek(), card)) { - // _onMove(onMove, giver, pile); - // } - // } - // } - // } else { - // // Cells and piles: - // // 1. To the base. - // for (int base = BASE_START + suit; base < BASE_END; base += Deck.SUIT_NUM) { - // if (_desk[base].size() == rank) { - // _onMove(onMove, giver, base); - // break; // one base is enough. - // } - // } - // // 2. To a tableau. - // for (int pile = PILE_START; pile < PILE_END; pile++) { - // if (!_desk[pile].isEmpty() && Deck.isTableau(_desk[pile].peek(), card)) { - // _onMove(onMove, giver, pile); - // } - // } - - // if (isCell(giver)) { - // // Cells only - // // 3. To an empty pile. - // if (emptyPile >= 0) { - // _onMove(onMove, giver, emptyPile); - // } - // } else { - // // It should be a pile then. - // // 3. To an empty cell. - // if (emptyCell >= 0) { - // _onMove(onMove, giver, emptyCell); - // } - // // 4. To an empty pile. - // if (emptyPile >= 0 && _desk[giver].size() > 1) { - // _onMove(onMove, giver, emptyPile); - // } - // } - // } - // } - // } - // } + public boolean canMoveToCell() { + final int taker = getEmptyCell(); + if (taker >= 0) { + for (int giver = PILE_START; giver < PILE_END; giver++) { + if (!_desk[giver].isEmpty() && isMoveForward(toMove(giver, taker))) { + return true; + } + } + } + return false; + } - public void getMoves(final IntStack moves) { - moves.clear(); + public void getMovesToCell(final IntStack moves) { + final int taker = getEmptyCell(); + if (taker >= 0) { + for (int giver = PILE_START; giver < PILE_END; giver++) { + if (!_desk[giver].isEmpty()) { + final int move = toMove(giver, taker); + if (isMoveForward(move)) { + moves.push(move); + } + } + } + } + } - // Get opposite color bases minimal ranks. - int ranks[] = { _desk[BASE_START].size(), _desk[BASE_START + 1].size() }; - for (int i = 2; i < BASE_NUM; i++) { - ranks[i & 1] = Math.min(ranks[i & 1], _desk[BASE_START + i].size()); + public boolean canMoveToPile() { + final int taker = getEmptyPile(); + if (taker >= 0) { + // 1. Test piles: + for (int giver = PILE_START; giver < PILE_END; giver++) { + if (_desk[giver].size() > 1 && isMoveForward(toMove(giver, taker))) { + return true; + } + } + // 2. Test cells: + for (int giver = CELL_START; giver < CELL_END; giver++) { + if (_desk[giver].size() > 0 && isMoveForward(toMove(giver, taker))) { + return true; + } + } } + return false; + } - final int emptyCell = getEmptyCell(); - final int emptyPile = getEmptyPile(); + public void getMovesToPile(final IntStack moves) { + final int taker = getEmptyPile(); + if (taker >= 0) { + // 1. Test piles: + for (int giver = PILE_START; giver < PILE_END; giver++) { + if (_desk[giver].size() > 1) { + final int move = toMove(giver, taker); + if (isMoveForward(move)) { + moves.push(move); + } + } + } + // 2. Test cells: + for (int giver = CELL_START; giver < CELL_END; giver++) { + if (_desk[giver].size() > 0) { + final int move = toMove(giver, taker); + if (isMoveForward(move)) { + moves.push(move); + } + } + } + } + } - for (int giver = DESK_SIZE; giver-- > 0;) { + public boolean canMoveToBase() { + // 1. Test piles: + for (int giver = PILE_START; giver < PILE_END; giver++) { if (!_desk[giver].isEmpty()) { - final int card = _desk[giver].peek(); + final byte card = _desk[giver].peek(); final int suit = Deck.suitOf(card); final int rank = Deck.rankOf(card); + for (int taker = BASE_START + suit; taker < BASE_END; taker += Deck.SUIT_NUM) { + if (_desk[taker].size() == rank) { + if (isMoveForward(toMove(giver, taker))) { + return true; + } + } + } + } + } - if (isBase(giver)) { - // We can take cards from bases only to form a tableau. - if (rank > ranks[(suit + 1) & 1] + 1) { - for (int pile = PILE_START; pile < PILE_END; pile++) { - if (!_desk[pile].isEmpty() && Deck.isTableau(_desk[pile].peek(), card)) { - moves.push(toMove(giver, pile)); - } + // 2. Test cells: + for (int giver = CELL_START; giver < CELL_END; giver++) { + if (!_desk[giver].isEmpty()) { + final byte card = _desk[giver].peek(); + final int suit = Deck.suitOf(card); + final int rank = Deck.rankOf(card); + for (int taker = BASE_START + suit; taker < BASE_END; taker += Deck.SUIT_NUM) { + if (_desk[taker].size() == rank) { + if (isMoveForward(toMove(giver, taker))) { + return true; + } + } + } + } + } + + return false; + } + + public void getMovesToBase(IntStack moves) { + // 1. Test piles: + for (int giver = PILE_START; giver < PILE_END; giver++) { + if (!_desk[giver].isEmpty()) { + final byte card = _desk[giver].peek(); + final int suit = Deck.suitOf(card); + final int rank = Deck.rankOf(card); + for (int taker = BASE_START + suit; taker < BASE_END; taker += Deck.SUIT_NUM) { + if (_desk[taker].size() == rank) { + final int move = toMove(giver, taker); + if (isMoveForward(move)) { + moves.push(move); + } + } + } + } + } + + // 2. Test cells: + for (int giver = CELL_START; giver < CELL_END; giver++) { + if (!_desk[giver].isEmpty()) { + final byte card = _desk[giver].peek(); + final int suit = Deck.suitOf(card); + final int rank = Deck.rankOf(card); + for (int taker = BASE_START + suit; taker < BASE_END; taker += Deck.SUIT_NUM) { + if (_desk[taker].size() == rank) { + final int move = toMove(giver, taker); + if (isMoveForward(move)) { + moves.push(move); } } - } else { - if (isCell(giver)) { - // Cells only - // 1. To an empty pile. - if (emptyPile >= 0) { - moves.push(toMove(giver, emptyPile)); + } + } + } + } + + // Get opposite color bases minimal ranks. + public int[] getBaseMinRanks() { + final int ranks[] = { _desk[BASE_START + 1].size(), _desk[BASE_START].size() }; + for (int i = 2; i < BASE_NUM;) { + ranks[1] = Math.min(ranks[1], _desk[BASE_START + i++].size()); + ranks[0] = Math.min(ranks[0], _desk[BASE_START + i++].size()); + } + return ranks; + } + + public boolean canMoveToTableau() { + // 1. Test piles: + for (int giver = PILE_START; giver < PILE_END; giver++) { + if (!_desk[giver].isEmpty()) { + final byte card = _desk[giver].peek(); + for (int taker = PILE_START; taker < PILE_END; taker++) { + if (giver != taker && !_desk[taker].isEmpty() && Deck.isTableau(_desk[taker].peek(), card)) { + if (isMoveForward(toMove(giver, taker))) { + return true; } - } else { - // It should be a pile then. - // 1. To an empty cell. - if (emptyCell >= 0) { - moves.push(toMove(giver, emptyCell)); + } + } + } + } + + // 2. Test cells: + for (int giver = CELL_START; giver < CELL_END; giver++) { + if (!_desk[giver].isEmpty()) { + final byte card = _desk[giver].peek(); + for (int taker = PILE_START; taker < PILE_END; taker++) { + if (!_desk[taker].isEmpty() && Deck.isTableau(_desk[taker].peek(), card)) { + if (isMoveForward(toMove(giver, taker))) { + return true; } - // 2. To an empty pile. - if (emptyPile >= 0 && _desk[giver].size() > 1) { - moves.push(toMove(giver, emptyPile)); + } + } + } + } + + // 3. Test bases: + // We can take cards from bases only to form a tableau. + + // Get opposite color bases minimal ranks. + final int ranks[] = getBaseMinRanks(); + + for (int giver = BASE_START; giver < BASE_END; giver++) { + if (!_desk[giver].isEmpty()) { + final byte card = _desk[giver].peek(); + final int rank = Deck.rankOf(card); + final int color = Deck.colorOf(card); + + if (rank > ranks[color] + 1) { + for (int taker = PILE_START; taker < PILE_END; taker++) { + if (!_desk[taker].isEmpty() && Deck.isTableau(_desk[taker].peek(), card)) { + if (isMoveForward(toMove(giver, taker))) { + return true; + } } } + } + } + } - // Cells and piles: - // 1. To a tableau. - for (int pile = PILE_START; pile < PILE_END; pile++) { - if (!_desk[pile].isEmpty() && Deck.isTableau(_desk[pile].peek(), card)) { - moves.push(toMove(giver, pile)); + return false; + } + + public void getMovesToTableau(IntStack moves) { + // 1. Test piles: + for (int giver = PILE_START; giver < PILE_END; giver++) { + if (!_desk[giver].isEmpty()) { + final byte card = _desk[giver].peek(); + for (int taker = PILE_START; taker < PILE_END; taker++) { + if (giver != taker && !_desk[taker].isEmpty() && Deck.isTableau(_desk[taker].peek(), card)) { + final int move = toMove(giver, taker); + if (isMoveForward(move)) { + moves.push(move); + } + } + } + } + } + + // 2. Test cells: + for (int giver = CELL_START; giver < CELL_END; giver++) { + if (!_desk[giver].isEmpty()) { + final byte card = _desk[giver].peek(); + for (int taker = PILE_START; taker < PILE_END; taker++) { + if (!_desk[taker].isEmpty() && Deck.isTableau(_desk[taker].peek(), card)) { + final int move = toMove(giver, taker); + if (isMoveForward(move)) { + moves.push(move); } } - // 2. To the base. - for (int base = BASE_START + suit; base < BASE_END; base += Deck.SUIT_NUM) { - if (_desk[base].size() == rank) { - if (rank <= ranks[(suit + 1) & 1] + 1) { - // It's a mandatory move to the base. Clear all other moves and return this move only. - moves.clear(); - moves.push(toMove(giver, base)); - return; - } else { - moves.push(toMove(giver, base)); - break; // one base is enough. + } + } + } + + // 3. Test bases: + // We can take cards from bases only to form a tableau. + + // Get opposite color bases minimal ranks. + final int ranks[] = getBaseMinRanks(); + + for (int giver = BASE_START; giver < BASE_END; giver++) { + if (!_desk[giver].isEmpty()) { + final byte card = _desk[giver].peek(); + final int rank = Deck.rankOf(card); + final int color = Deck.colorOf(card); + + if (rank > ranks[color] + 1) { + for (int taker = PILE_START; taker < PILE_END; taker++) { + if (!_desk[taker].isEmpty() && Deck.isTableau(_desk[taker].peek(), card)) { + final int move = toMove(giver, taker); + if (isMoveForward(move)) { + moves.push(move); } } } @@ -269,25 +365,29 @@ public void getMoves(final IntStack moves) { } } + public boolean hasNextMove() { + return canMoveToCell() || canMoveToPile() || canMoveToBase() || canMoveToTableau(); + } + + public void getMoves(final IntStack moves) { + moves.clear(); + getMovesToBase(moves); + getMovesToTableau(moves); + getMovesToCell(moves); + getMovesToPile(moves); + } + public void rewind() { backward(0); } - - // public int pathLength() { - // return _path.size(); - // } - - // public int[] pathToArray() { - // return _path.toArray​(); - // } /** * Makes a new deal. * * @param seed seed number */ - int[] deal(final int seed) { - final var cards = Deck.deck(seed); + byte[] deal(final int seed) { + final var cards = Deck.deal(seed); clear(); for (int i = 0; i < cards.length; i++) { @@ -296,4 +396,35 @@ int[] deal(final int seed) { return cards; } -} \ No newline at end of file + public static void main(String[] args) { + FreecellGame game = new FreecellGame(8, 4, 4); + final int seed = 417; + game.deal(seed); + + System.out.println("Deal: " + seed); + System.out.println("Game: " + game); + int count = game.moveCardsAuto(); + System.out.println("Auto: " + count); + System.out.println("Game: " + game); + System.out.println("Key: " + game.toKey()); + + System.out.println("Rewinding..."); + game.rewind(); + System.out.println("Game: " + game); + IntStack moves = new IntStack(); + count = 0; + while (true) { + moves.clear(); + game.getMovesToBase(moves); + if (moves.isEmpty()) { + break; + } else { + game.moveCard(moves.get(0)); + count++; + System.out.println("Move: " + count); + System.out.println("Key: " + game.toKey()); + } + } + System.out.println("Game: " + game); + } +} diff --git a/freecell/FreecellSolver.java b/freecell/FreecellSolver.java index 48553de..6cd27ee 100644 --- a/freecell/FreecellSolver.java +++ b/freecell/FreecellSolver.java @@ -1,264 +1,190 @@ package freecell; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; -// import java.util.HashSet; import java.util.List; import java.util.Map; -// import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; import common.Deck; import common.IntStack; public class FreecellSolver extends FreecellGame { - public FreecellSolver(int PILE_NUM, int CELL_NUM, int BASE_NUM) { - super(PILE_NUM, CELL_NUM, BASE_NUM); - } - - // private final Set _done = new HashSet<>(); - private Map _output = new HashMap<>(); - private final List> _stack = new ArrayList<>(); + public static final int + TOTAL_MAX = 5000000, + INPUT_MAX = 9000; - private int _iteration = 0; - private int _solution[]; - - // private static class SolvedException extends RuntimeException { - // /** - // * Serial UID - // */ - // private static final long serialVersionUID = 1L; - - // public SolvedException() { - // super(); - // } - // } - - private static class DeskState implements Comparable { - public final int free, solved; - public final String key; + public FreecellSolver() { + super(8, 4, 4); + } - public DeskState(String key, int free, int solved) { - this.key = key; - this.free = free; - this.solved = solved; - } + protected List _feed = new ArrayList<>(); + protected Map _done = new HashMap<>(); + protected SortedMap> _pool = new TreeMap<>(); + protected int[] _solution = null; + protected int _iteration = 0; + protected IntStack _nextMoves = new IntStack(); + protected Comparator _comparator = new Comparator<>() { @Override - public int compareTo(DeskState other) { - if (solved != other.solved) { - return other.solved - solved; + public int compare(int[] a, int[] b) { + if (a.length != b.length) { + return a.length - b.length; } - if (free != other.free) { - return other.free - free; + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + // Actually we can try to analize which move is better. + return a[i] - b[i]; + } } - return key.compareTo(other.key); + return 0; + } + }; + + protected boolean _shouldSolve(int pathLength) { + if (_solution != null) { + int cards = Deck.CARD_NUM - countSolved(); + return _solution.length > pathLength + cards; } + return true; } - // private final FreecellGame.OnMove _onMove = new FreecellGame.OnMove() { - // @Override - // public void test(FreecellGame game) { - // final var key = game.toKey(); - // if (!_shouldSolve()) { - // _done.remove(key); - // } else { - // if (!_done.contains(key)) { - // _done.add(key); - // _output.put(key, game.pathToArray()); - - // game.moveCardsToBases(); - // if (game.isSolved()) { - // if (_solution == null || _solution.length > game.pathLength()) { - // _solution = game.pathToArray(); - // System.out.println("*********\nSolution: " + _solution.length); - // System.out.println("Cleaning output: " + _output.size() + "\n*********\n"); - // _done.removeAll(_output.keySet()); - // _output.clear(); - // } - // } - // } - // } - // } - // }; + protected void _prepare() { + _feed.clear(); + _done.clear(); + _pool.clear(); - public void prepare() { - // _done.clear(); - _stack.clear(); - _output.clear(); + _solution = null; _iteration = 0; rewind(); moveCardsAuto(); - final var key = toKey(); - // _done.add(key); - _output.put(key, _path.toArray​()); - } - - private Map _getNextInput() { - final int INPUT_MAX = 50000; - if (_output.size() > 0) { - return _splitInput(_output, INPUT_MAX, INPUT_MAX / 10); - } - if (_stack.size() > 0) { - // System.out.println("Step back:" + _stack.size()); - // return _splitInput(_stack.remove(_stack.size() - 1), INPUT_MAX, INPUT_MAX / 3); - return _stack.remove(_stack.size() - 1); - } - - return null; + _done.put(toKey(), _path.size()); + _feed.add(_path.toArray​()); } - private Map _splitInput(Map input, int inputMax, int splitSize) { - final int inputSize = input.size(); - if (inputSize <= inputMax) { - return input; + protected void _splitOutput(final int outputMax) { + final int feedSize = _feed.size(); + if (feedSize > outputMax) { + for (final int[] path : _feed) { + rewind(); + forward(path); + + final int solved = countSolved(); + final int cells = countEmptyCells(); + final int piles = countEmptyPiles(); + final Integer KEY = Integer.valueOf( + (int) -Math.round(90.00 * solved + 3.00 * cells + 4.25 * piles - 2.75 * path.length) + ); + + if (_pool.containsKey(KEY)) { + _pool.get(KEY).add(path); + } else { + final SortedSet set = new TreeSet<>(_comparator); + set.add(path); + _pool.put(KEY, set); + } + } + _feed.clear(); } + } - var list = new ArrayList(inputSize); - // int countEmptyMax = 0; - // int countSolvedMax = 0; - for (final var key : input.keySet()) { - rewind(); - forward(input.get(key)); - final int countEmpty = countEmpty(); - final int countSolved = countSolved(); - - // countEmptyMax = Math.max(countEmptyMax, countEmpty); - // countSolvedMax = Math.max(countSolvedMax, countSolved); - - list.add(new DeskState(key, countEmpty, countSolved)); - } - - // var a = new ArrayList(); - // var b = new ArrayList(); - // for (int i = 0; i < inputSize; i++) { - // final var state = list.get(i); - // if (state.free == countEmptyMax || state.solved == countSolvedMax) { - // a.add(input.get(state.index)); - // } else { - // b.add(input.get(state.index)); - // } - // } - // System.out.println("\tSplitting input: " + input.size() + " => " + splitSize + " + " + (inputSize - splitSize)); - list.sort(null); + protected void _getNextInput() { + if (_feed.isEmpty() && !_pool.isEmpty()) { + final Integer KEY = _pool.firstKey(); + final var set = _pool.get(KEY); + final int size = set.first().length; + for (int[] path : set) { + if (size != path.length) { + break; + } else { + _feed.add(path); + } + } - while (input.size() > splitSize) { - final var buf = new HashMap(splitSize); - while (buf.size() < splitSize && input.size() > splitSize) { - final var key = list.get(input.size() - 1).key; - buf.put(key, input.remove(key)); + if (set.size() == _feed.size()) { + _pool.remove(KEY); + } else { + set.removeAll(_feed); } - _stack.add(buf); } - - // for (int i = 0; i < inputSize - splitSize; i++) { - // int index = list.get(i).index; - // b.add(input.get(index)); - // } - - - - // System.out.println("\tPath Length: " + input.get(0).length); - return input; } - private final boolean _shouldSolve(int pathLength) { - if (_solution != null) { - var cards = Deck.CARD_NUM - countSolved(); - return _solution.length > pathLength + cards; + protected boolean _nextIteration() { + final int doneSize = _done.size(); + if (_solution != null && doneSize > TOTAL_MAX) { + return false; } - return true; - } - // private boolean _removeIfLong(String key, int pathLength) { - // if (_solution != null) { - // if (pathLength + (Deck.CARD_NUM - countSolved()) >= _solution.length) { - // // _done.remove(key); - // return true; - // } - // } - // return false; - // } + final int oldSize = _pool.size(); - public boolean nextIteration() { - var input = _getNextInput(); - if (_output.size() > 0) { - _output = new HashMap(); - } + _splitOutput(INPUT_MAX); + _getNextInput(); - if (input == null) { - return false; - } - - final int inputSize = input.size(); - _iteration++; - if (_iteration > 5000) { + if (_feed.isEmpty()) { return false; } - if (inputSize <= 0) { - return false; + final int newSize = _pool.size(); + if (oldSize < newSize) { + System.out.print('+'); + } else if (oldSize > newSize) { + System.out.print('-'); + } else { + System.out.print('='); } - /* - System.out.println("#" + _iteration - + ": input=" + inputSize - // + ", done=" + _done.size() - + ", stack=" + _stack.size()); - */ - System.out.print('-'); + _iteration++; if (_iteration % 100 == 0) { - System.out.println("\n" + (_iteration / 100) + ":[" + _stack.size() + "]"); + System.out.println("\n" + (_iteration / 100) + + " [" + (doneSize * 100 / TOTAL_MAX) + + "% " + newSize + ']'); } - final IntStack moves = new IntStack(); - // for (final var entry : input.entrySet()) { - for (final var path : input.values()) { - // final var path = entry.getValue(); - final int mark = path.length; + final var input = _feed; + _feed = new ArrayList<>(); + + for (final int[] path : input) { rewind(); forward(path); - // if (!_removeIfLong(entry.getKey(), mark + 1)) { - if (_shouldSolve(mark + 1)) { - getMoves(moves); - while (moves.size() > 0) { - int move = moves.pop(); - if (isMoveForward(move)) { - moveCard(move); - final var key = toKey(); - // if (!_removeIfLong(key, mark + 1)) { - if (_shouldSolve(mark + 1)) { - // if (!_done.contains(key)) { - if (!input.containsKey(key) && !_output.containsKey(key)) { - // _done.add(key); - _output.put(key, _path.toArray​()); - - moveCardsToBases(); - if (isSolved()) { - if (_solution == null || _solution.length > _path.size()) { - _solution = _path.toArray​(); - System.out.println("\n*********\nSolution: " - + _solution.length - + '\n' + pathToString(_solution) - + "\n*********\n"); - } + if (_shouldSolve(path.length + 1)) { + getMoves(_nextMoves); + for (int i = 0; i < _nextMoves.size(); i++) { + backward(path.length); + moveCard(_nextMoves.get(i)); + if (_shouldSolve(_path.size()) && hasNextMove()) { + moveCardsAuto(); + String key = toKey(); + Integer value = _done.get(key); + if (value == null || value.intValue() > _path.size()) { + final int[] next = _path.toArray​(); + moveCardsToBases(); + if (isSolved()) { + if (_solution == null || _solution.length > _path.size()) { + _solution = _path.toArray​(); + System.out.println(); + System.out.println("*********"); + System.out.println("Solution: " + _solution.length); + System.out.println(pathToString(_solution)); + System.out.println("*********"); } + + } else { + _feed.add(next); + _done.put(key, Integer.valueOf(next.length)); } } - backward(mark); } } } } - return _output.size() > 0 || _stack.size() > 0; - } - - public int[] solve() { - prepare(); - while (nextIteration()); - return _solution; + return _feed.size() > 0 || _pool.size() > 0; } public static char toChar(int n) { @@ -278,9 +204,15 @@ public String pathToString(int[] path) { return buf.toString(); } + public int[] solve() { + _prepare(); + while (_nextIteration()); + return _solution; + } + public static void main(String[] args) { - final int deal = 25; - var game = new FreecellSolver(8, 4, 4); + final int deal = 3; + var game = new FreecellSolver(); game.deal(deal); System.out.println("DESK: " + game.toString()); System.out.println("KEY: " + game.toKey()); From 9c06aa39f39caf2a3bf11cae5654aa46087a03ba Mon Sep 17 00:00:00 2001 From: mico Date: Sun, 3 May 2020 16:53:36 +0300 Subject: [PATCH 09/16] update --- game-list.csv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/game-list.csv b/game-list.csv index c3dbdd2..7ab2b73 100644 --- a/game-list.csv +++ b/game-list.csv @@ -1,11 +1,11 @@ deal,mark,path 1,81,6c6d606e6b3656161f18101beb1e18e14e4048c83c32314b5b31393a4af17f737b5b707a79191a0a5aca272cdb2a272d29190928180879090a7828c9395158d80c0d0b6b18fb010a6a091a6bc9daeb4968 -2,83,486c646d696e6bc47674143c10e616193e3f39c95c50e05e59697909c9353c3a1a23292a3a7a7b2b245beb2e2878c8580c021318684858010845d8054d49090a0a4849474b5acadae8fb1a4a6b7a5b2b4b0b3b -3,86,594c5424202d24297e7b7854575a29576235636b151065161f1912187179f97f707a3739323a3b3818da4a616d68f84f4808d8490d292802c90958067a4645494b0c04090bfb0a4b1b4a6adb2a5a6bea2b5bcb28385a +2,82,481c105d506e646f6970606b1019e43e74d43d3639e95e59093579d9493d3a1a23292a3a7a7b2b5beb2e232878d858121808e8584d49424e414b6b0b05cb0b0608f828070938e90a0a7a5ada6a4a1a4b0b2b +3,84,594c4d7e7b78c4d45424202c2429295413101d1f19595af96212187f707a313739323aca4a6a6b3b3858616568f84c4849085829281845060249094101494b0f04090bcb0a4bdb4a5a6b1b2a1a2bea1b28387afb 4,80,3c3d3b563e505f5a5b0b3beb3e39c3f313181b010c0bdb2b2b632d6bcb2c2f2aca040c1a040a606a68f07f7aca754a753a7379474a4c4b4a1419ca091828495908485868e8384959d9187939f9386839 -5,83,287c707d7e474bc067346c3f39696b69e91e195919492924f414242b1b18183868512628525b58587f75287238783828023212f50103070f02050a6acada4a2a4b2b4a2a4b2b4a5b1a3b5aeb0b4a4a49f95979 +5,80,287c707d7e474b343f3967c06c696b69e91e195919492924f414242b1b18183868212852565b5858683805060356050f02070a1acada4a4b2a4a2b4b6b7b3b303a4a6aeb7a4a4a49f97971597808180b 6,85,4c4d4e404f4af4d4643d343fe35e535a7464fa676f636b101614171b195958787939f93f213929252b2a5bfa36387858090f757b78782807090b4b791937380309070a39586a284a6bfa184beb4ada4b7b4a78ca4b -7,87,1c1d5414543e101a1b18da3431313a387d7071c37c7a0f0a705ac01c1a5127c12c2a535425d524282962636d6b4b0bdb48080b2b056d690709f9491948181b4939483849397b7838ebd8094b4aca4b5a0b5909584c4aca +7,86,1c1d1e101a1b18da65616d6b4f461426f62f26212b7170e62e29727bdb0d020b01315b50353b343a38e85e567a2a5a4a545158786848080b181b656a6768386b686909d9e92959494a79c909fa1a19184b0b094c4aca 8,76,38631c6d1e19c9636956565c59585b1b242509e5391e1a2f292a35707a31303a6a4a5afa595a7f7b3b09750a7909fb0f0908e8386b4b4e47206168f918c81bdb18384beb284a084b580b0a7a 9,90,6c6d4e6f696bc92cd646262436262b37c35716171b5c575a2a1a6a3d303b4a6bfb47e24e6a4f48f41f1818565831353868737818717458757274527a7579f978c808070939690b1b49596a294a6b4b6adb2a0a0929e90a280b7b 10,87,303c3d3151515e5f5b3b3a51757b7818eb0e45057bf7454b4f6424f46f6bdb6deb6e68f838c3232023297919161c1f1b1519c9391c1a187a6adae878fa3a5a01260609395949030a5a4a5b6b0b5a0949394a48c8182868 From 1af20a41ca5366b19c3fde91bc7e90781640b77c Mon Sep 17 00:00:00 2001 From: mico Date: Sun, 3 May 2020 19:31:55 +0300 Subject: [PATCH 10/16] + debug flag and input size --- freecell/FreecellSolver.java | 59 ++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/freecell/FreecellSolver.java b/freecell/FreecellSolver.java index 6cd27ee..6de6556 100644 --- a/freecell/FreecellSolver.java +++ b/freecell/FreecellSolver.java @@ -16,10 +16,22 @@ public class FreecellSolver extends FreecellGame { public static final int TOTAL_MAX = 5000000, - INPUT_MAX = 9000; + INPUT_MIN = 1000, + INPUT_MAX = 100000; + + public boolean debug = false; public FreecellSolver() { super(8, 4, 4); + setInputSize(8888); + } + + public void setInputSize(int value) { + _inputSize = Math.min(Math.max(value, INPUT_MIN), INPUT_MAX); + } + + public int getInputSize() { + return _inputSize; } protected List _feed = new ArrayList<>(); @@ -28,6 +40,7 @@ public FreecellSolver() { protected int[] _solution = null; protected int _iteration = 0; protected IntStack _nextMoves = new IntStack(); + protected int _inputSize = INPUT_MIN; protected Comparator _comparator = new Comparator<>() { @Override @@ -37,7 +50,7 @@ public int compare(int[] a, int[] b) { } for (int i = 0; i < a.length; i++) { if (a[i] != b[i]) { - // Actually we can try to analize which move is better. + // Actually we could also analyze which move is better. return a[i] - b[i]; } } @@ -68,9 +81,9 @@ protected void _prepare() { _feed.add(_path.toArray​()); } - protected void _splitOutput(final int outputMax) { + protected void _splitOutput() { final int feedSize = _feed.size(); - if (feedSize > outputMax) { + if (feedSize > _inputSize) { for (final int[] path : _feed) { rewind(); forward(path); @@ -123,7 +136,7 @@ protected boolean _nextIteration() { final int oldSize = _pool.size(); - _splitOutput(INPUT_MAX); + _splitOutput(); _getNextInput(); if (_feed.isEmpty()) { @@ -131,16 +144,18 @@ protected boolean _nextIteration() { } final int newSize = _pool.size(); - if (oldSize < newSize) { - System.out.print('+'); - } else if (oldSize > newSize) { - System.out.print('-'); - } else { - System.out.print('='); + if (debug) { + if (oldSize < newSize) { + System.out.print('+'); + } else if (oldSize > newSize) { + System.out.print('-'); + } else { + System.out.print('='); + } } _iteration++; - if (_iteration % 100 == 0) { + if (debug && _iteration % 100 == 0) { System.out.println("\n" + (_iteration / 100) + " [" + (doneSize * 100 / TOTAL_MAX) + "% " + newSize + ']'); @@ -167,11 +182,14 @@ protected boolean _nextIteration() { if (isSolved()) { if (_solution == null || _solution.length > _path.size()) { _solution = _path.toArray​(); - System.out.println(); - System.out.println("*********"); - System.out.println("Solution: " + _solution.length); - System.out.println(pathToString(_solution)); - System.out.println("*********"); + + if (debug) { + System.out.println(); + System.out.println("*********"); + System.out.println("Solution: " + _solution.length); + System.out.println(pathToString(_solution)); + System.out.println("*********"); + } } } else { @@ -211,8 +229,9 @@ public int[] solve() { } public static void main(String[] args) { - final int deal = 3; + final int deal = 8; var game = new FreecellSolver(); + game.debug = true; game.deal(deal); System.out.println("DESK: " + game.toString()); System.out.println("KEY: " + game.toKey()); @@ -223,8 +242,8 @@ public static void main(String[] args) { System.out.println("" + deal + ',' + path.length + ',' + game.pathToString(path)); - game.rewind(); - game.forward(path); + // game.rewind(); + // game.forward(path); } else { System.out.println("Unsolved ;-("); } From a3436521ab53b0d713d6583c80b8968764df5e8b Mon Sep 17 00:00:00 2001 From: mico Date: Sun, 3 May 2020 19:32:19 +0300 Subject: [PATCH 11/16] + solution --- freecell/FreecellSolution.java | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 freecell/FreecellSolution.java diff --git a/freecell/FreecellSolution.java b/freecell/FreecellSolution.java new file mode 100644 index 0000000..343221e --- /dev/null +++ b/freecell/FreecellSolution.java @@ -0,0 +1,70 @@ +package freecell; + +public class FreecellSolution { + public static class Commands { + public static final String + d = "-d", + deal = "--deal", + s = "-s", + split = "--split"; + } + + public static void main(String[] args) { + if (args.length < 1 || (args.length & 1) == 1) { + usage(); + return; + } + + final var game = new FreecellSolver(); + game.debug = false; + int deal = -1; + + for (int i = 0; i < args.length; i+=2) { + String command = args[i]; + try { + int value = Integer.decode(args[i + 1]); + + if (command.equalsIgnoreCase(Commands.d) + || command.equalsIgnoreCase(Commands.deal)) { + deal = value; + game.deal(deal); + } else if (command.equalsIgnoreCase(Commands.s) + || command.equalsIgnoreCase(Commands.split)) { + game.setInputSize(value); + } + } catch (NumberFormatException ex) { + System.err.println("Couldn't parse integer from '" + args[i + 1] + + "' for command '" + command + "'"); + usage(); + return; + } + } + + if (deal < 0) { + System.err.println("Deal number should be > 0."); + usage(); + return; + } + + var path = game.solve(); + if (path != null) { + System.out.println("" + deal + ',' + path.length + ',' + game.pathToString(path)); + System.exit(0); + } else { + System.err.println("Unsolved ;-("); + System.exit(1); + } + } + + public static void usage() { + System.out.println("Usage:"); + System.out.println("\t" + Commands.d + ", " + Commands.deal + + "\t\tdeal number (>= 0)"); + System.out.println("\t" + Commands.s + ", " + Commands.split + + "\t\tinput split size [" + + FreecellSolver.INPUT_MIN + ", " + + FreecellSolver.INPUT_MAX + "]" + ); + System.exit(-1); + } +} From a49afb1b1755b21bca5a4c9218ba507208a67904 Mon Sep 17 00:00:00 2001 From: mico Date: Mon, 4 May 2020 11:17:46 +0300 Subject: [PATCH 12/16] cleaning --- common/ByteStack.java | 2 +- common/IntStack.java | 2 +- freecell/FreecellSolver.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/ByteStack.java b/common/ByteStack.java index 753113e..4fff72c 100644 --- a/common/ByteStack.java +++ b/common/ByteStack.java @@ -96,7 +96,7 @@ public byte get(final int index) { * Returns an array containing all of the elements in this stack. * @return an array */ - public byte[] toArray​() { + public byte[] toArray() { return Arrays.copyOf(_data, _size); } diff --git a/common/IntStack.java b/common/IntStack.java index 1515e0f..0c528b0 100644 --- a/common/IntStack.java +++ b/common/IntStack.java @@ -84,7 +84,7 @@ public int get(final int index) { * Returns an array containing all of the elements in this stack. * @return an array */ - public int[] toArray​() { + public int[] toArray() { return Arrays.copyOf(_data, _size); } diff --git a/freecell/FreecellSolver.java b/freecell/FreecellSolver.java index 6de6556..7360eab 100644 --- a/freecell/FreecellSolver.java +++ b/freecell/FreecellSolver.java @@ -78,7 +78,7 @@ protected void _prepare() { moveCardsAuto(); _done.put(toKey(), _path.size()); - _feed.add(_path.toArray​()); + _feed.add(_path.toArray()); } protected void _splitOutput() { @@ -177,11 +177,11 @@ protected boolean _nextIteration() { String key = toKey(); Integer value = _done.get(key); if (value == null || value.intValue() > _path.size()) { - final int[] next = _path.toArray​(); + final int[] next = _path.toArray(); moveCardsToBases(); if (isSolved()) { if (_solution == null || _solution.length > _path.size()) { - _solution = _path.toArray​(); + _solution = _path.toArray(); if (debug) { System.out.println(); From a37a9ed7a71fcf67aea6eaaf0425619752086cb6 Mon Sep 17 00:00:00 2001 From: mico Date: Mon, 4 May 2020 11:18:19 +0300 Subject: [PATCH 13/16] freecell compile and run --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 2073a25..9487894 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,18 @@ # java small java projects + +## Freecell Solver +### Compile +```shell +javac freecell\FreecellSolution.java +``` +### Run +```shell +java freecell.FreecellSolution --deal 1 +``` +### JAR File +```shell +jar cvfe freecell-solver.jar freecell.FreecellSolution freecell\*.class common\*.class +java -jar freecell-solver.jar --deal 1 +``` + From 92cebf863ffc751ea4915b97fb924b9ec2072516 Mon Sep 17 00:00:00 2001 From: mico Date: Mon, 4 May 2020 11:21:45 +0300 Subject: [PATCH 14/16] make it final --- freecell/FreecellSolver.java | 70 ++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/freecell/FreecellSolver.java b/freecell/FreecellSolver.java index 7360eab..e47aea1 100644 --- a/freecell/FreecellSolver.java +++ b/freecell/FreecellSolver.java @@ -13,7 +13,7 @@ import common.Deck; import common.IntStack; -public class FreecellSolver extends FreecellGame { +public final class FreecellSolver extends FreecellGame { public static final int TOTAL_MAX = 5000000, INPUT_MIN = 1000, @@ -26,7 +26,7 @@ public FreecellSolver() { setInputSize(8888); } - public void setInputSize(int value) { + public void setInputSize(final int value) { _inputSize = Math.min(Math.max(value, INPUT_MIN), INPUT_MAX); } @@ -34,17 +34,17 @@ public int getInputSize() { return _inputSize; } - protected List _feed = new ArrayList<>(); - protected Map _done = new HashMap<>(); - protected SortedMap> _pool = new TreeMap<>(); - protected int[] _solution = null; - protected int _iteration = 0; - protected IntStack _nextMoves = new IntStack(); - protected int _inputSize = INPUT_MIN; + private List _feed = new ArrayList<>(); + private final Map _done = new HashMap<>(); + private final SortedMap> _pool = new TreeMap<>(); + private int[] _solution = null; + private int _iteration = 0; + private final IntStack _nextMoves = new IntStack(); + private int _inputSize = INPUT_MIN; - protected Comparator _comparator = new Comparator<>() { + private final Comparator _comparator = new Comparator<>() { @Override - public int compare(int[] a, int[] b) { + public int compare(final int[] a, final int[] b) { if (a.length != b.length) { return a.length - b.length; } @@ -58,15 +58,15 @@ public int compare(int[] a, int[] b) { } }; - protected boolean _shouldSolve(int pathLength) { + private boolean _shouldSolve(final int pathLength) { if (_solution != null) { - int cards = Deck.CARD_NUM - countSolved(); + final int cards = Deck.CARD_NUM - countSolved(); return _solution.length > pathLength + cards; } return true; } - protected void _prepare() { + private void _prepare() { _feed.clear(); _done.clear(); _pool.clear(); @@ -81,7 +81,7 @@ protected void _prepare() { _feed.add(_path.toArray()); } - protected void _splitOutput() { + private void _splitOutput() { final int feedSize = _feed.size(); if (feedSize > _inputSize) { for (final int[] path : _feed) { @@ -91,9 +91,8 @@ protected void _splitOutput() { final int solved = countSolved(); final int cells = countEmptyCells(); final int piles = countEmptyPiles(); - final Integer KEY = Integer.valueOf( - (int) -Math.round(90.00 * solved + 3.00 * cells + 4.25 * piles - 2.75 * path.length) - ); + final Integer KEY = Integer + .valueOf((int) -Math.round(90.00 * solved + 3.00 * cells + 4.25 * piles - 2.75 * path.length)); if (_pool.containsKey(KEY)) { _pool.get(KEY).add(path); @@ -107,12 +106,12 @@ protected void _splitOutput() { } } - protected void _getNextInput() { + private void _getNextInput() { if (_feed.isEmpty() && !_pool.isEmpty()) { final Integer KEY = _pool.firstKey(); final var set = _pool.get(KEY); final int size = set.first().length; - for (int[] path : set) { + for (final int[] path : set) { if (size != path.length) { break; } else { @@ -128,7 +127,7 @@ protected void _getNextInput() { } } - protected boolean _nextIteration() { + private boolean _nextIteration() { final int doneSize = _done.size(); if (_solution != null && doneSize > TOTAL_MAX) { return false; @@ -156,9 +155,7 @@ protected boolean _nextIteration() { _iteration++; if (debug && _iteration % 100 == 0) { - System.out.println("\n" + (_iteration / 100) - + " [" + (doneSize * 100 / TOTAL_MAX) - + "% " + newSize + ']'); + System.out.println("\n" + (_iteration / 100) + " [" + (doneSize * 100 / TOTAL_MAX) + "% " + newSize + ']'); } final var input = _feed; @@ -174,8 +171,8 @@ protected boolean _nextIteration() { moveCard(_nextMoves.get(i)); if (_shouldSolve(_path.size()) && hasNextMove()) { moveCardsAuto(); - String key = toKey(); - Integer value = _done.get(key); + final String key = toKey(); + final Integer value = _done.get(key); if (value == null || value.intValue() > _path.size()) { final int[] next = _path.toArray(); moveCardsToBases(); @@ -205,17 +202,17 @@ protected boolean _nextIteration() { return _feed.size() > 0 || _pool.size() > 0; } - public static char toChar(int n) { + public static char toChar(final int n) { if (n >= 0 && n < 10) { - return (char)('0' + n); + return (char) ('0' + n); } else { - return (char)('a' + n - 10); + return (char) ('a' + n - 10); } } - public String pathToString(int[] path) { - var buf = new StringBuilder(path.length); - for (var move : path) { + public String pathToString(final int[] path) { + final var buf = new StringBuilder(path.length); + for (final var move : path) { buf.append(toChar(toGiver(move))); buf.append(toChar(toTaker(move))); } @@ -224,19 +221,20 @@ public String pathToString(int[] path) { public int[] solve() { _prepare(); - while (_nextIteration()); + while (_nextIteration()) + ; return _solution; } - public static void main(String[] args) { + public static void main(final String[] args) { final int deal = 8; - var game = new FreecellSolver(); + final var game = new FreecellSolver(); game.debug = true; game.deal(deal); System.out.println("DESK: " + game.toString()); System.out.println("KEY: " + game.toKey()); - var path = game.solve(); + final var path = game.solve(); if (path != null) { System.out.println("Solved!"); System.out.println("" + deal From cf2af93794d8636681be93aeac4d30e5f1979fdd Mon Sep 17 00:00:00 2001 From: mico Date: Mon, 4 May 2020 13:17:46 +0300 Subject: [PATCH 15/16] moving build files into the build folder --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9487894..6767ec6 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,16 @@ small java projects ## Freecell Solver ### Compile ```shell -javac freecell\FreecellSolution.java +mkdir build && javac common/*.java freecell/*.java -d build ``` ### Run ```shell -java freecell.FreecellSolution --deal 1 +pushd build && java freecell.FreecellSolution --deal 1 && popd ``` ### JAR File ```shell -jar cvfe freecell-solver.jar freecell.FreecellSolution freecell\*.class common\*.class -java -jar freecell-solver.jar --deal 1 +pushd build +jar cvfe freecell-solver.jar freecell.FreecellSolution common/*.class freecell/*.class +popd +java -jar build/freecell-solver.jar --deal 1 ``` - From 7483f9a61856a03c97fabf356b982cd731c5fa6c Mon Sep 17 00:00:00 2001 From: mico Date: Mon, 4 May 2020 13:26:38 +0300 Subject: [PATCH 16/16] ~ txt to csv --- freecell-solver-test.csv | 10 +++ test.txt | 128 --------------------------------------- 2 files changed, 10 insertions(+), 128 deletions(-) create mode 100644 freecell-solver-test.csv delete mode 100644 test.txt diff --git a/freecell-solver-test.csv b/freecell-solver-test.csv new file mode 100644 index 0000000..17f7aeb --- /dev/null +++ b/freecell-solver-test.csv @@ -0,0 +1,10 @@ +1,85,151c186d60106e6f6b1bfb1f184140484bd85d535bf51f313616e1313e3535393a4a73767b707a795ac90a2727272c2928590958085b5a0b1a79197a0a79e8286954580e010bc8db3b48040a096a4a1a6b68e949fb +2,83,481c106d64d46d69676b17195e5036303f396956597935363a1a707303747909e92e292aea7a7b2b75272e28585b68586b121838785801050608d80d070a0ada1a4a497948494d4bfb3bcb5b6a4a4b0b2bdae8 +3,94,5925202c2529292d7e7b7813101fd2621d191218616b63f65f56f65f56595ad902434d34d4f43d7f727a37d73d3949393a3b3858ca6a1a01454909074c494b570502090b0a6b4a2b7a606b1a6768f808282b78287b2b7828387bca5beada +4,82,56503c3d3b5e5a5b0b5f353b5be515181b3e3901030bdb2b2b656b3b252d262ada1a0405040af07d7370707f79171969606a6818280879093a5a4a41525a4a434b4a3a48084968e8f809291959d9185868c9 +5,89,28614c4b3434396d696b6965c6161c1e1f14151818385158737020d17d79e959f9c71c2e242b1b2f2851525b5858454b1b28eb3b38010e02e2120131010e0b0afacada5aea4a3a49294a2a2b6a7b7c780b4a4a49196918c819 +6,98,536c7d7e72d262e7676d656b4e46c64c40104f4af41f12171b1914c15c515a59587879c9d91c193d31363a30387858d12d252656d62d2429252b2a5b7b7878ca0c090105090b286b59490509070aca6a1afb6b1b6a1a3c386b6ad82848597b4bc8ea +7,89,651c1d1e101a1b18da6d6f6b2141462621d4e12d2b2e29d272707d7bfb7f07e70e0b5b0250c2513c313b343a3878faea5a0e0bcb6c6b6f690979194a545258781848d9191a49194acae8281829092a29384b0b09f8284c4aca +8,83,3856561c1d19c95c56585b1b020e050f08181ad10d05e575762e24747b20303525252a6a3a3b4b636a6b4a4b0b42616b61693938e9191a595a0a61681819595ac818384c4b2b4a4b2e28dbe8787959f95aca59 +9,91,7475756c6d65696bc976797c27c72c25232b13d71d13231e194f4247f20f0949c94c4248181851541451596952d25d525a3a3bea3a4a353a393e3b5b1a50584b7a373138080b282b2a6a78187b2b2a0a0978297b0a0bd8c9e828fb +10,87,7c7d787151515e575b5804203435252625297919d13d3f3b3a1beb1e1b3563623213121419f91f1a7aea6e676a3a6368e8581838595a4a010309590e0a454a46494b7b7849db5b1a2bca4a486bf8283b0b0929e92c28c8 diff --git a/test.txt b/test.txt deleted file mode 100644 index e68a9e6..0000000 --- a/test.txt +++ /dev/null @@ -1,128 +0,0 @@ -DESK: KDKC7C6H9C9HAD,8S7S3C6D8C5HJH,QS2C2DAHASACQH,2SKS4H4C7D5SJD,THQC3HKHTCJC,5D9S3S7H8H8D,JS3D4D4S9D6C,TS6S2H5CTDQD,,,,,,,, -KEY: ____2SKS4H4C7D5SJD,5D9S3S7H8H8D,8S7S3C6D8C5HJH,JS3D4D4S9D6C,KDKC7C6H9C9HAD,QS2C2DAHASACQH,THQC3HKHTCJC,TS6S2H5CTDQD ---------------------------------------------------------- -********* -Solution: 82 -091c16101d2e2a282b292a1ad132313d3a31e3437e737a7b424f4b1b6b6a0602030b530ae05e5beb2b4e4b346acb3a3c303b62fb3f3858685361691959187819d9184918293878c92a680a29ea0928f80a09 -********* - -- -********* -Solution: 81 -091c16101d2e2a282b292a1ad132313d3a31e3437e737a7b424f4b1b6b6a0602030b530ae05e5beb2b4e4bcb3c6a3a342a3aea3b3e38586368546169fb1959187819d918c9183948784968290928e80a09 -********* - ------------------------------------------- -1:[300] ----------------------------------------------------------------------------------------------------- -2:[281] ----------------------------------------------------------------------------------------------------- -3:[270] ----------------------------------------------------------------------------------------------------- -4:[264] ---- -********* -Solution: 80 -092c2a282b292a1216101d1ad13d31303a313e38e37343737a7b424e4b1b0f6b6a0602030b530ac0d46a5c5b58cb6c685d612b691959187819f91839c9183ad82a3949783a4a68394b2b0b2838eb0a09 -********* - -------------------------------------------------------------------------------------------------- -5:[238] ----------------------------------------------------------------------------------------------------- -6:[224] ----------------------------------------------------------------------------------------------------- -7:[231] --------------------------------------------------------------- -********* -Solution: 79 -092c2a282b292a1216101d1ad13d31303a313e38e37343737a7b424e4b1b0f6b6a0602030b530ac05c5b586acb5c2b626861691959187819f9183918293ac82a3978d93a4a68394b2b0b2838eb0a09 -********* - --------------------------------------- -8:[226] ----------------------------------------------------------------------------------------------------- -9:[208] ----------------------------------------------------------------------------------------------------- -10:[239] ----------------------------------------------------------------------------------------------------- -11:[219] ----------------------------------------------------------------------------------------------------- -12:[203] ----------------------------------------------------------------------------------------------------- -13:[214] ----------------------------------------------------------------------------------------------------- -14:[205] ----------------------------------------------------------------------------------------------------- -15:[244] ----------------------------------------------------------------------------------------------------- -16:[222] ----------------------------------------------------------------------------------------------------- -17:[208] ----------------------------------------------------------------------------------------------------- -18:[227] ----------------------------------------------------------------------------------------------------- -19:[203] ----------------------------------------------------------------------------------------------------- -20:[206] ----------------------------------------------------------------------------------------------------- -21:[215] ----------------------------------------------------------------------------------------------------- -22:[194] ----------------------------------------------------------------------------------------------------- -23:[200] ----------------------------------------------------------------------------------------------------- -24:[213] ----------------------------------------------------------------------------------------------------- -25:[198] ----------------------------------------------------------------------------------------------------- -26:[225] ----------------------------------------------------------------------------------------------------- -27:[232] ----------------------------------------------------------------------------------------------------- -28:[211] ----------------------------------------------------------------------------------------------------- -29:[199] ----------------------------------------------------------------------------------------------------- -30:[197] ----------------------------------------------------------------------------------------------------- -31:[195] ----------------------------------------------------------------------------------------------------- -32:[195] ----------------------------------------------------------------------------------------------------- -33:[195] ----------------------------------------------------------------------------------------------------- -34:[218] ----------------------------------------------------------------------------------------------------- -35:[207] ----------------------------------------------------------------------------------------------------- -36:[191] ----------------------------------------------------------------------------------------------------- -37:[208] ----------------------------------------------------------------------------------------------------- -38:[184] ----------------------------------------------------------------------------------------------------- -39:[185] ----------------------------------------------------------------------------------------------------- -40:[174] ----------------------------------------------------------------------------------------------------- -41:[220] ----------------------------------------------------------------------------------------------------- -42:[202] ----------------------------------------------------------------------------------------------------- -43:[215] ----------------------------------------------------------------------------------------------------- -44:[210] ----------------------------------------------------------------------------------------------------- -45:[194] ----------------------------------------------------------------------------------------------------- -46:[206] ----------------------------------------------------------------------------------------------------- -47:[206] ----------------------------------------------------------------------------------------------------- -48:[195] ----------------------------------------------------------------------------------------------------- -49:[189] ----------------------------------------------------------------------------------------------------- -50:[272] -Solved! -24,79,092c2a282b292a1216101d1ad13d31303a313e38e37343737a7b424e4b1b0f6b6a0602030b530ac05c5b586acb5c2b626861691959187819f9183918293ac82a3978d93a4a68394b2b0b2838eb0a09