Compare commits
6 Commits
6565792030
...
c++
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28546cf5e9 | ||
|
|
cd1bc39bad | ||
|
|
5af5282aab | ||
|
|
de72585989 | ||
|
|
a6cca47d99 | ||
|
|
8cd39b10c2 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
test_config/
|
test_config/
|
||||||
|
build/
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "shenzhen_solitaire/c++/cmake"]
|
||||||
|
path = shenzhen_solitaire/c++/cmake
|
||||||
|
url = git@github.com:corrodedHash/cmake-framework.git
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -9,5 +9,6 @@
|
|||||||
"python.testing.pytestEnabled": false,
|
"python.testing.pytestEnabled": false,
|
||||||
"python.testing.nosetestsEnabled": false,
|
"python.testing.nosetestsEnabled": false,
|
||||||
"python.testing.unittestEnabled": true,
|
"python.testing.unittestEnabled": true,
|
||||||
"python.pythonPath": "/home/lukas/.local/share/virtualenvs/shenzhen-solitaire-nsu5dgrx/bin/python"
|
"python.pythonPath": "/home/lukas/.local/share/virtualenvs/shenzhen-solitaire-nsu5dgrx/bin/python",
|
||||||
|
"python.formatting.provider": "black"
|
||||||
}
|
}
|
||||||
21
setup.py
Normal file
21
setup.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from distutils.core import setup, Extension
|
||||||
|
|
||||||
|
module1 = Extension(
|
||||||
|
"shenzhen_solitaire._shenzhen_solitaire",
|
||||||
|
sources=["shenzhen_solitaire/c++/main.i", "shenzhen_solitaire/c++/main.cpp",],
|
||||||
|
swig_opts=["-c++", "-py3"],
|
||||||
|
)
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="shenzhen_solitaire",
|
||||||
|
version="1.0",
|
||||||
|
description="This is a demo package",
|
||||||
|
packages=[
|
||||||
|
"shenzhen_solitaire",
|
||||||
|
"shenzhen_solitaire.card_detection",
|
||||||
|
"shenzhen_solitaire.clicker",
|
||||||
|
"shenzhen_solitaire.solver",
|
||||||
|
],
|
||||||
|
ext_modules=[module1],
|
||||||
|
)
|
||||||
|
|
||||||
@@ -59,53 +59,63 @@ class Board:
|
|||||||
MAX_COLUMN_SIZE = 8
|
MAX_COLUMN_SIZE = 8
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.field: List[List[Card]] = [[]] * Board.MAX_COLUMN_SIZE
|
self._field: List[List[Card]] = [[]] * Board.MAX_COLUMN_SIZE
|
||||||
self.bunker: List[Union[Tuple[SpecialCard, int], Optional[Card]]] = [None] * 3
|
self._bunker: List[Union[Tuple[SpecialCard, int], Optional[Card]]] = [None] * 3
|
||||||
self.goal: List[Optional[NumberCard]] = [None] * 3
|
self._goal: List[Optional[NumberCard]] = [None] * 3
|
||||||
self.flower_gone: bool = False
|
self._flower_gone: bool = False
|
||||||
|
|
||||||
def getGoal(self, suit: NumberCard.Suit) -> int:
|
def getGoal(self, suit: NumberCard.Suit) -> int:
|
||||||
for card in self.goal:
|
for card in self._goal:
|
||||||
if card is not None and card.suit == suit:
|
if card is not None and card.suit == suit:
|
||||||
return card.number
|
return card.number
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def setField(self, field: List[List[Card]]) -> None:
|
||||||
|
assert len(field) == 8
|
||||||
|
self._field = field
|
||||||
|
|
||||||
def getGoalId(self, suit: NumberCard.Suit) -> int:
|
def getGoalId(self, suit: NumberCard.Suit) -> int:
|
||||||
for index, card in enumerate(self.goal):
|
for index, card in enumerate(self._goal):
|
||||||
if card is not None and card.suit == suit:
|
if card is not None and card.suit == suit:
|
||||||
return index
|
return index
|
||||||
else:
|
else:
|
||||||
return self.goal.index(None)
|
return self._goal.index(None)
|
||||||
|
|
||||||
def setGoal(self, suit: NumberCard.Suit, value: int) -> None:
|
def setGoal(self, suit: NumberCard.Suit, value: int) -> None:
|
||||||
assert len(self.goal) == 3
|
assert len(self._goal) == 3
|
||||||
assert 0 <= value
|
assert 0 <= value
|
||||||
assert value <= 9
|
assert value <= 9
|
||||||
if value == 0:
|
if value == 0:
|
||||||
self.goal[self.getGoalId(suit)] = None
|
self._goal[self.getGoalId(suit)] = None
|
||||||
else:
|
else:
|
||||||
self.goal[self.getGoalId(suit)] = NumberCard(suit, number=value)
|
self._goal[self.getGoalId(suit)] = NumberCard(suit, number=value)
|
||||||
|
|
||||||
def incGoal(self, suit: NumberCard.Suit) -> None:
|
def incGoal(self, suit: NumberCard.Suit) -> None:
|
||||||
self.setGoal(suit, self.getGoal(suit) + 1)
|
self.setGoal(suit, self.getGoal(suit) + 1)
|
||||||
|
|
||||||
def solved(self) -> bool:
|
def solved(self) -> bool:
|
||||||
"""Returns true if the board is solved"""
|
"""Returns true if the board is solved"""
|
||||||
if any(x.number != 9 for x in self.goal if x is not None):
|
if any(x.number != 9 for x in self._goal if x is not None):
|
||||||
return False
|
return False
|
||||||
if any(not isinstance(x, tuple) for x in self.bunker):
|
if any(not isinstance(x, tuple) for x in self._bunker):
|
||||||
return False
|
return False
|
||||||
if not self.flower_gone:
|
if not self._flower_gone:
|
||||||
return False
|
return False
|
||||||
assert all(not x for x in self.field)
|
assert all(not x for x in self._field)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def getField(self) -> List[List[Card]]:
|
||||||
|
return self._field
|
||||||
|
|
||||||
|
def getBunker(self) -> List[Union[Tuple[SpecialCard, int], Optional[Card]]]:
|
||||||
|
return self._bunker
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_identifier(self) -> int:
|
def state_identifier(self) -> int:
|
||||||
"""Returns a unique identifier to represent the board state"""
|
"""Returns a unique identifier to represent the board state"""
|
||||||
result: int = 0
|
result: int = 0
|
||||||
for card in self.bunker:
|
for card in self._bunker:
|
||||||
result <<= 2
|
result <<= 2
|
||||||
if isinstance(card, tuple):
|
if isinstance(card, tuple):
|
||||||
result |= 0
|
result |= 0
|
||||||
@@ -119,12 +129,12 @@ class Board:
|
|||||||
result |= card.identifier()
|
result |= card.identifier()
|
||||||
|
|
||||||
result <<= 1
|
result <<= 1
|
||||||
if self.flower_gone:
|
if self._flower_gone:
|
||||||
result |= 1
|
result |= 1
|
||||||
|
|
||||||
assert len(self.goal) == 3
|
assert len(self._goal) == 3
|
||||||
suit_sequence = list(NumberCard.Suit)
|
suit_sequence = list(NumberCard.Suit)
|
||||||
for card in self.goal:
|
for card in self._goal:
|
||||||
result <<= 5
|
result <<= 5
|
||||||
if card is None:
|
if card is None:
|
||||||
result |= len(suit_sequence) * 10
|
result |= len(suit_sequence) * 10
|
||||||
@@ -134,12 +144,12 @@ class Board:
|
|||||||
# Max stack size is 13
|
# Max stack size is 13
|
||||||
# (4 random cards from the start, plus a stack from 9 to 1)
|
# (4 random cards from the start, plus a stack from 9 to 1)
|
||||||
# So 4 bits are sufficient
|
# So 4 bits are sufficient
|
||||||
for stack in self.field:
|
for stack in self._field:
|
||||||
assert len(stack) == len(stack) & 0b1111
|
assert len(stack) == len(stack) & 0b1111
|
||||||
result <<= 4
|
result <<= 4
|
||||||
result |= len(stack)
|
result |= len(stack)
|
||||||
|
|
||||||
for field_card in itertools.chain.from_iterable(self.field):
|
for field_card in itertools.chain.from_iterable(self._field):
|
||||||
result <<= 5
|
result <<= 5
|
||||||
result |= field_card.identifier()
|
result |= field_card.identifier()
|
||||||
|
|
||||||
@@ -159,12 +169,12 @@ class Board:
|
|||||||
SpecialCard.Hua: 0,
|
SpecialCard.Hua: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.flower_gone:
|
if self._flower_gone == True:
|
||||||
special_cards[SpecialCard.Hua] += 1
|
special_cards[SpecialCard.Hua] += 1
|
||||||
|
|
||||||
for card in itertools.chain(
|
for card in itertools.chain(
|
||||||
self.bunker,
|
self._bunker,
|
||||||
itertools.chain.from_iterable(stack for stack in self.field if stack),
|
itertools.chain.from_iterable(stack for stack in self._field if stack),
|
||||||
):
|
):
|
||||||
if isinstance(card, tuple):
|
if isinstance(card, tuple):
|
||||||
special_cards[card[0]] += 4
|
special_cards[card[0]] += 4
|
||||||
|
|||||||
1
shenzhen_solitaire/c++/.gitignore
vendored
Normal file
1
shenzhen_solitaire/c++/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
build*/
|
||||||
11
shenzhen_solitaire/c++/CMakeLists.txt
Normal file
11
shenzhen_solitaire/c++/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
project("solitaire" LANGUAGES CXX)
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||||
|
include( "common" )
|
||||||
|
|
||||||
|
add_library(board STATIC lib/board.cpp lib/card.cpp lib/goal.cpp)
|
||||||
|
set_property(TARGET board PROPERTY CXX_STANDARD 17)
|
||||||
|
target_include_directories(board PUBLIC include)
|
||||||
|
add_subdirectory(auxiliary)
|
||||||
12
shenzhen_solitaire/c++/auxiliary/CMakeLists.txt
Normal file
12
shenzhen_solitaire/c++/auxiliary/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
find_package(SWIG REQUIRED)
|
||||||
|
include(${SWIG_USE_FILE})
|
||||||
|
|
||||||
|
find_package(PythonLibs REQUIRED)
|
||||||
|
|
||||||
|
set_property(SOURCE swig.i PROPERTY CPLUSPLUS ON)
|
||||||
|
swig_add_library(shenzhen_python TYPE SHARED LANGUAGE PYTHON SOURCES swig.i)
|
||||||
|
|
||||||
|
target_include_directories(shenzhen_python PUBLIC ${PYTHON_INCLUDE_PATH})
|
||||||
|
target_link_libraries(shenzhen_python PUBLIC board)
|
||||||
|
|
||||||
|
set_property(TARGET shenzhen_python PROPERTY CXX_STANDARD 17)
|
||||||
57
shenzhen_solitaire/c++/auxiliary/swig.i
Normal file
57
shenzhen_solitaire/c++/auxiliary/swig.i
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
%module shenzhen
|
||||||
|
%{
|
||||||
|
#include "board.hpp"
|
||||||
|
#include "card.hpp"
|
||||||
|
#include "goal.hpp"
|
||||||
|
%}
|
||||||
|
|
||||||
|
%include "std_array.i"
|
||||||
|
%include "std_string.i"
|
||||||
|
|
||||||
|
namespace solitaire {
|
||||||
|
enum class CardType : int { Zhong, Bai, Fa, Hua, Red, Green, Black };
|
||||||
|
|
||||||
|
auto isNormalCardType(CardType type) -> bool;
|
||||||
|
|
||||||
|
struct Card {
|
||||||
|
CardType type;
|
||||||
|
int value;
|
||||||
|
auto toString() const noexcept -> std::string;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Goal {
|
||||||
|
std::array<std::optional<Card>, 3> goal{};
|
||||||
|
auto getEmptyId() -> std::optional<int>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
auto getId(CardType suit) const noexcept -> std::optional<int>;
|
||||||
|
auto get(CardType suit) const noexcept -> std::optional<int>;
|
||||||
|
|
||||||
|
void set(CardType suit, int value) noexcept;
|
||||||
|
void inc(CardType suit) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BunkerField {
|
||||||
|
std::optional<Card> card;
|
||||||
|
bool empty{};
|
||||||
|
bool closed{};
|
||||||
|
};
|
||||||
|
|
||||||
|
inline constexpr int MAX_ROW_SIZE = 13;
|
||||||
|
inline constexpr int MAX_COLUMN_SIZE = 8;
|
||||||
|
|
||||||
|
class Stack {
|
||||||
|
std::array<std::optional<Card>, MAX_COLUMN_SIZE> values{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Board {
|
||||||
|
std::array<Stack, MAX_ROW_SIZE> field{};
|
||||||
|
std::array<BunkerField, 3> bunker{};
|
||||||
|
Goal goal{};
|
||||||
|
bool flower_gone{};
|
||||||
|
|
||||||
|
auto solved() const noexcept -> bool;
|
||||||
|
auto hash() const noexcept -> std::size_t;
|
||||||
|
auto correct() const noexcept -> bool;
|
||||||
|
};
|
||||||
|
}
|
||||||
1
shenzhen_solitaire/c++/cmake
Submodule
1
shenzhen_solitaire/c++/cmake
Submodule
Submodule shenzhen_solitaire/c++/cmake added at c888dc5541
35
shenzhen_solitaire/c++/include/board.hpp
Normal file
35
shenzhen_solitaire/c++/include/board.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "card.hpp"
|
||||||
|
#include "goal.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace solitaire {
|
||||||
|
|
||||||
|
struct BunkerField {
|
||||||
|
std::optional<Card> card;
|
||||||
|
bool empty{};
|
||||||
|
bool closed{};
|
||||||
|
};
|
||||||
|
|
||||||
|
inline constexpr int MAX_ROW_SIZE = 13;
|
||||||
|
inline constexpr int MAX_COLUMN_SIZE = 8;
|
||||||
|
|
||||||
|
class Stack {
|
||||||
|
std::array<std::optional<Card>, MAX_COLUMN_SIZE> values{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Board {
|
||||||
|
std::array<Stack, MAX_ROW_SIZE> field{};
|
||||||
|
std::array<BunkerField, 3> bunker{};
|
||||||
|
Goal goal{};
|
||||||
|
bool flower_gone{};
|
||||||
|
|
||||||
|
[[nodiscard]] auto solved() const noexcept -> bool;
|
||||||
|
[[nodiscard]] auto hash() const noexcept -> std::size_t;
|
||||||
|
[[nodiscard]] auto correct() const noexcept -> bool;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace solitaire
|
||||||
15
shenzhen_solitaire/c++/include/card.hpp
Normal file
15
shenzhen_solitaire/c++/include/card.hpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cassert>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace solitaire {
|
||||||
|
enum class CardType : int { Zhong, Bai, Fa, Hua, Red, Green, Black };
|
||||||
|
|
||||||
|
auto isNormalCardType(CardType type) -> bool;
|
||||||
|
|
||||||
|
struct Card {
|
||||||
|
CardType type;
|
||||||
|
int value;
|
||||||
|
[[nodiscard]] auto toString() const noexcept -> std::string;
|
||||||
|
};
|
||||||
|
} // namespace solitaire
|
||||||
20
shenzhen_solitaire/c++/include/goal.hpp
Normal file
20
shenzhen_solitaire/c++/include/goal.hpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "card.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace solitaire {
|
||||||
|
class Goal {
|
||||||
|
std::array<std::optional<Card>, 3> goal{};
|
||||||
|
auto getEmptyId() -> std::optional<int>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
[[nodiscard]] auto getId(CardType suit) const noexcept -> std::optional<int>;
|
||||||
|
[[nodiscard]] auto get(CardType suit) const noexcept -> std::optional<int>;
|
||||||
|
|
||||||
|
void set(CardType suit, int value) noexcept;
|
||||||
|
void inc(CardType suit) noexcept;
|
||||||
|
};
|
||||||
|
} // namespace solitaire
|
||||||
1
shenzhen_solitaire/c++/lib/board.cpp
Normal file
1
shenzhen_solitaire/c++/lib/board.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "board.hpp"
|
||||||
15
shenzhen_solitaire/c++/lib/card.cpp
Normal file
15
shenzhen_solitaire/c++/lib/card.cpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#include "card.hpp"
|
||||||
|
namespace solitaire {
|
||||||
|
auto
|
||||||
|
isNormalCardType(CardType type) -> bool {
|
||||||
|
switch (type) {
|
||||||
|
case CardType::Red:
|
||||||
|
case CardType::Green:
|
||||||
|
case CardType::Black:
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace solitaire
|
||||||
66
shenzhen_solitaire/c++/lib/goal.cpp
Normal file
66
shenzhen_solitaire/c++/lib/goal.cpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
#include "goal.hpp"
|
||||||
|
|
||||||
|
namespace solitaire {
|
||||||
|
auto
|
||||||
|
Goal::getEmptyId() -> std::optional<int> {
|
||||||
|
int counter = 0;
|
||||||
|
for (const auto& slot : goal) {
|
||||||
|
if (!slot) {
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto
|
||||||
|
Goal::getId(CardType suit) const noexcept -> std::optional<int> {
|
||||||
|
int counter = 0;
|
||||||
|
for (const auto& slot : goal) {
|
||||||
|
if (slot && slot->type == suit) {
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto
|
||||||
|
Goal::get(CardType suit) const noexcept -> std::optional<int> {
|
||||||
|
if (auto index = getId(suit); index) {
|
||||||
|
return goal[*index]->value;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Goal::set(CardType suit, int value) noexcept {
|
||||||
|
assert(value >= 0);
|
||||||
|
assert(value <= 9);
|
||||||
|
|
||||||
|
const auto card = [&]() -> std::optional<Card> {
|
||||||
|
if (value == 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return Card{suit, value};
|
||||||
|
}();
|
||||||
|
|
||||||
|
const int goal_index = [&]() -> int {
|
||||||
|
if (auto index = getId(suit); index) {
|
||||||
|
return *index;
|
||||||
|
}
|
||||||
|
return *getEmptyId();
|
||||||
|
}();
|
||||||
|
|
||||||
|
goal[goal_index] = card;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Goal::inc(CardType suit) noexcept {
|
||||||
|
auto get_value = get(suit);
|
||||||
|
int new_value = get_value ? (*get_value) + 1 : 1;
|
||||||
|
set(suit, new_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace solitaire
|
||||||
@@ -225,8 +225,8 @@ def parse_goal(image: np.ndarray, conf: Configuration) -> List[Optional[NumberCa
|
|||||||
|
|
||||||
def parse_board(image: np.ndarray, conf: Configuration) -> Board:
|
def parse_board(image: np.ndarray, conf: Configuration) -> Board:
|
||||||
result = Board()
|
result = Board()
|
||||||
result.field = parse_field(image, conf)
|
result.setField(parse_field(image, conf))
|
||||||
|
result.setGoal(parse_goal(image, conf))
|
||||||
result.flower_gone = parse_hua(image, conf)
|
result.flower_gone = parse_hua(image, conf)
|
||||||
result.bunker = parse_bunker(image, conf)
|
result.bunker = parse_bunker(image, conf)
|
||||||
result.goal = parse_goal(image, conf)
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -129,15 +129,15 @@ class MoveAction(Action):
|
|||||||
"""Shift a card from the field id 'source' to field id 'dest'"""
|
"""Shift a card from the field id 'source' to field id 'dest'"""
|
||||||
|
|
||||||
for stack_offset, card in enumerate(self.cards, start=-len(self.cards)):
|
for stack_offset, card in enumerate(self.cards, start=-len(self.cards)):
|
||||||
assert action_board.field[source][stack_offset] == card
|
assert action_board.getField()[source][stack_offset] == card
|
||||||
|
|
||||||
action_board.field[source] = action_board.field[source][: -len(self.cards)]
|
action_board.getField()[source] = action_board.getField()[source][: -len(self.cards)]
|
||||||
action_board.field[dest].extend(self.cards)
|
action_board.getField()[dest].extend(self.cards)
|
||||||
|
|
||||||
def _apply(self, action_board: board.Board) -> None:
|
def _apply(self, action_board: board.Board) -> None:
|
||||||
"""Do action"""
|
"""Do action"""
|
||||||
if action_board.field[self.destination_id]:
|
if action_board.getField()[self.destination_id]:
|
||||||
dest_card = action_board.field[self.destination_id][-1]
|
dest_card = action_board.getField()[self.destination_id][-1]
|
||||||
if not all(isinstance(x, board.NumberCard) for x in self.cards):
|
if not all(isinstance(x, board.NumberCard) for x in self.cards):
|
||||||
raise AssertionError()
|
raise AssertionError()
|
||||||
if not isinstance(dest_card, board.NumberCard):
|
if not isinstance(dest_card, board.NumberCard):
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ def possible_huakill_action(
|
|||||||
search_board: board.Board,
|
search_board: board.Board,
|
||||||
) -> Iterator[board_actions.HuaKillAction]:
|
) -> Iterator[board_actions.HuaKillAction]:
|
||||||
"""Check if the flowercard can be eliminated"""
|
"""Check if the flowercard can be eliminated"""
|
||||||
for index, stack in enumerate(search_board.field):
|
for index, stack in enumerate(search_board.getField()):
|
||||||
if stack and stack[-1] == board.SpecialCard.Hua:
|
if stack and stack[-1] == board.SpecialCard.Hua:
|
||||||
yield board_actions.HuaKillAction(
|
yield board_actions.HuaKillAction(
|
||||||
source_field_id=index, source_field_row_index=len(stack) - 1
|
source_field_id=index, source_field_row_index=len(stack) - 1
|
||||||
@@ -25,17 +25,17 @@ def possible_dragonkill_actions(
|
|||||||
board.SpecialCard.Fa,
|
board.SpecialCard.Fa,
|
||||||
board.SpecialCard.Bai,
|
board.SpecialCard.Bai,
|
||||||
]
|
]
|
||||||
if not any(x is None for x in search_board.bunker):
|
if not any(x is None for x in search_board.getBunker()):
|
||||||
new_possible_dragons = []
|
new_possible_dragons = []
|
||||||
for dragon in possible_dragons:
|
for dragon in possible_dragons:
|
||||||
if any(x == dragon for x in search_board.bunker):
|
if any(x == dragon for x in search_board.getBunker()):
|
||||||
new_possible_dragons.append(dragon)
|
new_possible_dragons.append(dragon)
|
||||||
possible_dragons = new_possible_dragons
|
possible_dragons = new_possible_dragons
|
||||||
|
|
||||||
for dragon in possible_dragons:
|
for dragon in possible_dragons:
|
||||||
bunker_dragons = [i for i, d in enumerate(search_board.bunker) if d == dragon]
|
bunker_dragons = [i for i, d in enumerate(search_board.getBunker()) if d == dragon]
|
||||||
field_dragons = [
|
field_dragons = [
|
||||||
i for i, f in enumerate(search_board.field) if f if f[-1] == dragon
|
i for i, f in enumerate(search_board.getField()) if f if f[-1] == dragon
|
||||||
]
|
]
|
||||||
if len(bunker_dragons) + len(field_dragons) != 4:
|
if len(bunker_dragons) + len(field_dragons) != 4:
|
||||||
continue
|
continue
|
||||||
@@ -44,7 +44,7 @@ def possible_dragonkill_actions(
|
|||||||
destination_bunker_id = bunker_dragons[0]
|
destination_bunker_id = bunker_dragons[0]
|
||||||
else:
|
else:
|
||||||
destination_bunker_id = [
|
destination_bunker_id = [
|
||||||
i for i, x in enumerate(search_board.bunker) if x is None
|
i for i, x in enumerate(search_board.getBunker()) if x is None
|
||||||
][0]
|
][0]
|
||||||
|
|
||||||
source_stacks = [(board.Position.Bunker, i) for i in bunker_dragons]
|
source_stacks = [(board.Position.Bunker, i) for i in bunker_dragons]
|
||||||
@@ -61,13 +61,13 @@ def possible_bunkerize_actions(
|
|||||||
search_board: board.Board,
|
search_board: board.Board,
|
||||||
) -> Iterator[board_actions.BunkerizeAction]:
|
) -> Iterator[board_actions.BunkerizeAction]:
|
||||||
"""Enumerates all possible card moves from the field to the bunker"""
|
"""Enumerates all possible card moves from the field to the bunker"""
|
||||||
open_bunker_list = [i for i, x in enumerate(search_board.bunker) if x is None]
|
open_bunker_list = [i for i, x in enumerate(search_board.getBunker()) if x is None]
|
||||||
|
|
||||||
if not open_bunker_list:
|
if not open_bunker_list:
|
||||||
return
|
return
|
||||||
|
|
||||||
open_bunker = open_bunker_list[0]
|
open_bunker = open_bunker_list[0]
|
||||||
for index, stack in enumerate(search_board.field):
|
for index, stack in enumerate(search_board.getField()):
|
||||||
if not stack:
|
if not stack:
|
||||||
continue
|
continue
|
||||||
yield board_actions.BunkerizeAction(
|
yield board_actions.BunkerizeAction(
|
||||||
@@ -85,11 +85,11 @@ def possible_debunkerize_actions(
|
|||||||
"""Enumerates all possible card moves from the bunker to the field"""
|
"""Enumerates all possible card moves from the bunker to the field"""
|
||||||
bunker_number_cards = [
|
bunker_number_cards = [
|
||||||
(i, x)
|
(i, x)
|
||||||
for i, x in enumerate(search_board.bunker)
|
for i, x in enumerate(search_board.getBunker())
|
||||||
if isinstance(x, board.NumberCard)
|
if isinstance(x, board.NumberCard)
|
||||||
]
|
]
|
||||||
for index, card in bunker_number_cards:
|
for index, card in bunker_number_cards:
|
||||||
for other_index, other_stack in enumerate(search_board.field):
|
for other_index, other_stack in enumerate(search_board.getField()):
|
||||||
if not other_stack:
|
if not other_stack:
|
||||||
continue
|
continue
|
||||||
if not isinstance(other_stack[-1], board.NumberCard):
|
if not isinstance(other_stack[-1], board.NumberCard):
|
||||||
@@ -113,12 +113,12 @@ def possible_goal_move_actions(
|
|||||||
"""Enumerates all possible moves from anywhere to the goal"""
|
"""Enumerates all possible moves from anywhere to the goal"""
|
||||||
field_cards = [
|
field_cards = [
|
||||||
(board.Position.Field, index, stack[-1])
|
(board.Position.Field, index, stack[-1])
|
||||||
for index, stack in enumerate(search_board.field)
|
for index, stack in enumerate(search_board.getField())
|
||||||
if stack
|
if stack
|
||||||
]
|
]
|
||||||
bunker_cards = [
|
bunker_cards = [
|
||||||
(board.Position.Bunker, index, card)
|
(board.Position.Bunker, index, card)
|
||||||
for index, card in enumerate(search_board.bunker)
|
for index, card in enumerate(search_board.getBunker())
|
||||||
]
|
]
|
||||||
top_cards = [
|
top_cards = [
|
||||||
x for x in field_cards + bunker_cards if isinstance(x[2], board.NumberCard)
|
x for x in field_cards + bunker_cards if isinstance(x[2], board.NumberCard)
|
||||||
@@ -129,6 +129,7 @@ def possible_goal_move_actions(
|
|||||||
|
|
||||||
result = []
|
result = []
|
||||||
for source, index, card in top_cards:
|
for source, index, card in top_cards:
|
||||||
|
assert isinstance(card, board.NumberCard)
|
||||||
obvious = all(
|
obvious = all(
|
||||||
search_board.getGoal(other_suit) >= card.number - 2
|
search_board.getGoal(other_suit) >= card.number - 2
|
||||||
for other_suit in set(board.NumberCard.Suit) - {card.suit}
|
for other_suit in set(board.NumberCard.Suit) - {card.suit}
|
||||||
@@ -137,7 +138,7 @@ def possible_goal_move_actions(
|
|||||||
board_actions.GoalAction(
|
board_actions.GoalAction(
|
||||||
card=card,
|
card=card,
|
||||||
source_id=index,
|
source_id=index,
|
||||||
source_row_index=len(search_board.field[index]) - 1
|
source_row_index=len(search_board.getField()[index]) - 1
|
||||||
if source == board.Position.Field
|
if source == board.Position.Field
|
||||||
else None,
|
else None,
|
||||||
source_position=source,
|
source_position=source,
|
||||||
@@ -164,7 +165,7 @@ def _can_stack(bottom: board.Card, top: board.Card) -> bool:
|
|||||||
def _get_cardstacks(search_board: board.Board) -> List[List[board.Card]]:
|
def _get_cardstacks(search_board: board.Board) -> List[List[board.Card]]:
|
||||||
"""Returns all cards on one stack that can be moved at once"""
|
"""Returns all cards on one stack that can be moved at once"""
|
||||||
result: List[List[board.Card]] = []
|
result: List[List[board.Card]] = []
|
||||||
for stack in search_board.field:
|
for stack in search_board.getField():
|
||||||
result.append([])
|
result.append([])
|
||||||
if not stack:
|
if not stack:
|
||||||
continue
|
continue
|
||||||
@@ -194,13 +195,13 @@ def possible_field_move_actions(
|
|||||||
)
|
)
|
||||||
|
|
||||||
for source_index, source_substack in substacks:
|
for source_index, source_substack in substacks:
|
||||||
for destination_index, destination_stack in enumerate(search_board.field):
|
for destination_index, destination_stack in enumerate(search_board.getField()):
|
||||||
if source_index == destination_index:
|
if source_index == destination_index:
|
||||||
continue
|
continue
|
||||||
if destination_stack:
|
if destination_stack:
|
||||||
if not _can_stack(destination_stack[-1], source_substack[0]):
|
if not _can_stack(destination_stack[-1], source_substack[0]):
|
||||||
continue
|
continue
|
||||||
elif len(source_substack) == len(search_board.field[source_index]):
|
elif len(source_substack) == len(search_board.getField()[source_index]):
|
||||||
continue
|
continue
|
||||||
elif first_empty_field_id == -1:
|
elif first_empty_field_id == -1:
|
||||||
first_empty_field_id = destination_index
|
first_empty_field_id = destination_index
|
||||||
@@ -209,7 +210,7 @@ def possible_field_move_actions(
|
|||||||
yield board_actions.MoveAction(
|
yield board_actions.MoveAction(
|
||||||
cards=source_substack,
|
cards=source_substack,
|
||||||
source_id=source_index,
|
source_id=source_index,
|
||||||
source_row_index=len(search_board.field[source_index])
|
source_row_index=len(search_board.getField()[source_index])
|
||||||
- len(source_substack),
|
- len(source_substack),
|
||||||
destination_id=destination_index,
|
destination_id=destination_index,
|
||||||
destination_row_index=len(destination_stack),
|
destination_row_index=len(destination_stack),
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ from shenzhen_solitaire.board import Board, NumberCard, SpecialCard
|
|||||||
|
|
||||||
Suit = NumberCard.Suit
|
Suit = NumberCard.Suit
|
||||||
|
|
||||||
TEST_BOARD = Board()
|
_TEST_BOARD_FIELDS = [None] * 8
|
||||||
TEST_BOARD.field[0] = [
|
_TEST_BOARD_FIELDS[0] = [
|
||||||
SpecialCard.Fa,
|
SpecialCard.Fa,
|
||||||
NumberCard(NumberCard.Suit.Black, 8),
|
NumberCard(NumberCard.Suit.Black, 8),
|
||||||
SpecialCard.Bai,
|
SpecialCard.Bai,
|
||||||
@@ -12,7 +12,7 @@ TEST_BOARD.field[0] = [
|
|||||||
SpecialCard.Zhong,
|
SpecialCard.Zhong,
|
||||||
]
|
]
|
||||||
|
|
||||||
TEST_BOARD.field[1] = [
|
_TEST_BOARD_FIELDS[1] = [
|
||||||
NumberCard(NumberCard.Suit.Red, 9),
|
NumberCard(NumberCard.Suit.Red, 9),
|
||||||
SpecialCard.Zhong,
|
SpecialCard.Zhong,
|
||||||
SpecialCard.Zhong,
|
SpecialCard.Zhong,
|
||||||
@@ -20,7 +20,7 @@ TEST_BOARD.field[1] = [
|
|||||||
NumberCard(NumberCard.Suit.Black, 3),
|
NumberCard(NumberCard.Suit.Black, 3),
|
||||||
]
|
]
|
||||||
|
|
||||||
TEST_BOARD.field[2] = [
|
_TEST_BOARD_FIELDS[2] = [
|
||||||
SpecialCard.Hua,
|
SpecialCard.Hua,
|
||||||
NumberCard(NumberCard.Suit.Red, 1),
|
NumberCard(NumberCard.Suit.Red, 1),
|
||||||
NumberCard(NumberCard.Suit.Red, 4),
|
NumberCard(NumberCard.Suit.Red, 4),
|
||||||
@@ -28,7 +28,7 @@ TEST_BOARD.field[2] = [
|
|||||||
NumberCard(NumberCard.Suit.Red, 6),
|
NumberCard(NumberCard.Suit.Red, 6),
|
||||||
]
|
]
|
||||||
|
|
||||||
TEST_BOARD.field[3] = [
|
_TEST_BOARD_FIELDS[3] = [
|
||||||
SpecialCard.Bai,
|
SpecialCard.Bai,
|
||||||
SpecialCard.Zhong,
|
SpecialCard.Zhong,
|
||||||
NumberCard(NumberCard.Suit.Red, 3),
|
NumberCard(NumberCard.Suit.Red, 3),
|
||||||
@@ -36,7 +36,7 @@ TEST_BOARD.field[3] = [
|
|||||||
NumberCard(NumberCard.Suit.Green, 6),
|
NumberCard(NumberCard.Suit.Green, 6),
|
||||||
]
|
]
|
||||||
|
|
||||||
TEST_BOARD.field[4] = [
|
_TEST_BOARD_FIELDS[4] = [
|
||||||
NumberCard(NumberCard.Suit.Green, 7),
|
NumberCard(NumberCard.Suit.Green, 7),
|
||||||
NumberCard(NumberCard.Suit.Green, 4),
|
NumberCard(NumberCard.Suit.Green, 4),
|
||||||
NumberCard(NumberCard.Suit.Red, 5),
|
NumberCard(NumberCard.Suit.Red, 5),
|
||||||
@@ -44,7 +44,7 @@ TEST_BOARD.field[4] = [
|
|||||||
NumberCard(NumberCard.Suit.Black, 6),
|
NumberCard(NumberCard.Suit.Black, 6),
|
||||||
]
|
]
|
||||||
|
|
||||||
TEST_BOARD.field[5] = [
|
_TEST_BOARD_FIELDS[5] = [
|
||||||
NumberCard(NumberCard.Suit.Green, 3),
|
NumberCard(NumberCard.Suit.Green, 3),
|
||||||
SpecialCard.Bai,
|
SpecialCard.Bai,
|
||||||
SpecialCard.Fa,
|
SpecialCard.Fa,
|
||||||
@@ -52,7 +52,7 @@ TEST_BOARD.field[5] = [
|
|||||||
NumberCard(NumberCard.Suit.Black, 5),
|
NumberCard(NumberCard.Suit.Black, 5),
|
||||||
]
|
]
|
||||||
|
|
||||||
TEST_BOARD.field[6] = [
|
_TEST_BOARD_FIELDS[6] = [
|
||||||
SpecialCard.Fa,
|
SpecialCard.Fa,
|
||||||
NumberCard(NumberCard.Suit.Green, 9),
|
NumberCard(NumberCard.Suit.Green, 9),
|
||||||
NumberCard(NumberCard.Suit.Green, 2),
|
NumberCard(NumberCard.Suit.Green, 2),
|
||||||
@@ -60,16 +60,18 @@ TEST_BOARD.field[6] = [
|
|||||||
NumberCard(NumberCard.Suit.Red, 8),
|
NumberCard(NumberCard.Suit.Red, 8),
|
||||||
]
|
]
|
||||||
|
|
||||||
TEST_BOARD.field[7] = [
|
_TEST_BOARD_FIELDS[7] = [
|
||||||
SpecialCard.Bai,
|
SpecialCard.Bai,
|
||||||
NumberCard(NumberCard.Suit.Red, 2),
|
NumberCard(NumberCard.Suit.Red, 2),
|
||||||
SpecialCard.Fa,
|
SpecialCard.Fa,
|
||||||
NumberCard(NumberCard.Suit.Black, 1),
|
NumberCard(NumberCard.Suit.Black, 1),
|
||||||
NumberCard(NumberCard.Suit.Green, 8),
|
NumberCard(NumberCard.Suit.Green, 8),
|
||||||
]
|
]
|
||||||
|
TEST_BOARD = Board()
|
||||||
|
TEST_BOARD.setField(_TEST_BOARD_FIELDS)
|
||||||
|
|
||||||
B20190809172206_1 = Board()
|
_B20190809172206_1_FIELDS = [None] * 8
|
||||||
B20190809172206_1.field[0] = [
|
_B20190809172206_1_FIELDS[0] = [
|
||||||
NumberCard(Suit.Green, 6),
|
NumberCard(Suit.Green, 6),
|
||||||
NumberCard(Suit.Green, 5),
|
NumberCard(Suit.Green, 5),
|
||||||
NumberCard(Suit.Red, 4),
|
NumberCard(Suit.Red, 4),
|
||||||
@@ -77,7 +79,7 @@ B20190809172206_1.field[0] = [
|
|||||||
SpecialCard.Fa,
|
SpecialCard.Fa,
|
||||||
]
|
]
|
||||||
|
|
||||||
B20190809172206_1.field[1] = [
|
_B20190809172206_1_FIELDS[1] = [
|
||||||
NumberCard(Suit.Black, 8),
|
NumberCard(Suit.Black, 8),
|
||||||
NumberCard(Suit.Black, 6),
|
NumberCard(Suit.Black, 6),
|
||||||
SpecialCard.Zhong,
|
SpecialCard.Zhong,
|
||||||
@@ -85,33 +87,33 @@ B20190809172206_1.field[1] = [
|
|||||||
NumberCard(Suit.Green, 7),
|
NumberCard(Suit.Green, 7),
|
||||||
]
|
]
|
||||||
|
|
||||||
B20190809172206_1.field[2] = [
|
_B20190809172206_1_FIELDS[2] = [
|
||||||
SpecialCard.Zhong,
|
SpecialCard.Zhong,
|
||||||
NumberCard(Suit.Black, 4),
|
NumberCard(Suit.Black, 4),
|
||||||
NumberCard(Suit.Green, 2),
|
NumberCard(Suit.Green, 2),
|
||||||
SpecialCard.Bai,
|
SpecialCard.Bai,
|
||||||
SpecialCard.Zhong,
|
SpecialCard.Zhong,
|
||||||
]
|
]
|
||||||
B20190809172206_1.field[3] = [
|
_B20190809172206_1_FIELDS[3] = [
|
||||||
NumberCard(Suit.Green, 1),
|
NumberCard(Suit.Green, 1),
|
||||||
NumberCard(Suit.Green, 3),
|
NumberCard(Suit.Green, 3),
|
||||||
NumberCard(Suit.Black, 5),
|
NumberCard(Suit.Black, 5),
|
||||||
SpecialCard.Fa,
|
SpecialCard.Fa,
|
||||||
SpecialCard.Fa,
|
SpecialCard.Fa,
|
||||||
]
|
]
|
||||||
B20190809172206_1.field[4] = [
|
_B20190809172206_1_FIELDS[4] = [
|
||||||
NumberCard(Suit.Red, 8),
|
NumberCard(Suit.Red, 8),
|
||||||
SpecialCard.Zhong,
|
SpecialCard.Zhong,
|
||||||
NumberCard(Suit.Red, 7),
|
NumberCard(Suit.Red, 7),
|
||||||
]
|
]
|
||||||
B20190809172206_1.field[5] = [
|
_B20190809172206_1_FIELDS[5] = [
|
||||||
SpecialCard.Fa,
|
SpecialCard.Fa,
|
||||||
SpecialCard.Bai,
|
SpecialCard.Bai,
|
||||||
NumberCard(Suit.Red, 2),
|
NumberCard(Suit.Red, 2),
|
||||||
SpecialCard.Hua,
|
SpecialCard.Hua,
|
||||||
SpecialCard.Bai,
|
SpecialCard.Bai,
|
||||||
]
|
]
|
||||||
B20190809172206_1.field[6] = [
|
_B20190809172206_1_FIELDS[6] = [
|
||||||
NumberCard(Suit.Black, 2),
|
NumberCard(Suit.Black, 2),
|
||||||
NumberCard(Suit.Green, 8),
|
NumberCard(Suit.Green, 8),
|
||||||
NumberCard(Suit.Black, 7),
|
NumberCard(Suit.Black, 7),
|
||||||
@@ -119,10 +121,12 @@ B20190809172206_1.field[6] = [
|
|||||||
NumberCard(Suit.Red, 9),
|
NumberCard(Suit.Red, 9),
|
||||||
]
|
]
|
||||||
|
|
||||||
B20190809172206_1.field[7] = [
|
_B20190809172206_1_FIELDS[7] = [
|
||||||
NumberCard(Suit.Red, 3),
|
NumberCard(Suit.Red, 3),
|
||||||
NumberCard(Suit.Black, 3),
|
NumberCard(Suit.Black, 3),
|
||||||
NumberCard(Suit.Green, 9),
|
NumberCard(Suit.Green, 9),
|
||||||
NumberCard(Suit.Red, 5),
|
NumberCard(Suit.Red, 5),
|
||||||
NumberCard(Suit.Red, 6),
|
NumberCard(Suit.Red, 6),
|
||||||
]
|
]
|
||||||
|
B20190809172206_1 = Board()
|
||||||
|
B20190809172206_1.setField(_B20190809172206_1_FIELDS)
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ class CardDetectionTest(unittest.TestCase):
|
|||||||
loaded_config = configuration.load("test_config.zip")
|
loaded_config = configuration.load("test_config.zip")
|
||||||
my_board = board_parser.parse_board(image, loaded_config)
|
my_board = board_parser.parse_board(image, loaded_config)
|
||||||
|
|
||||||
for correct_row, my_row in zip(boards.B20190809172206_1.field, my_board.field):
|
for correct_row, my_row in zip(boards.B20190809172206_1.getField(), my_board.getField()):
|
||||||
|
print(correct_row)
|
||||||
|
print(my_row)
|
||||||
self.assertListEqual(correct_row, my_row)
|
self.assertListEqual(correct_row, my_row)
|
||||||
|
|
||||||
def test_hua_detection(self) -> None:
|
def test_hua_detection(self) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user