Compare commits

..

6 Commits

Author SHA1 Message Date
Lukas Wölfer
28546cf5e9 Worked a little more on decoupling 2020-03-24 14:45:48 +01:00
Lukas Wölfer
cd1bc39bad Started integrating c++ into python 2020-03-21 00:16:14 +01:00
Lukas Wölfer
5af5282aab Worked on c++ 2020-03-20 00:12:32 +01:00
Lukas Wölfer
de72585989 Added gitignore to c++ project 2020-03-15 23:57:55 +01:00
Lukas Wölfer
a6cca47d99 Started working on c++ 2020-03-15 23:56:05 +01:00
Lukas Wölfer
8cd39b10c2 Started working on c++ extension 2020-02-17 02:18:56 +01:00
28 changed files with 363 additions and 374 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
test_config/ test_config/
build/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "shenzhen_solitaire/c++/cmake"]
path = shenzhen_solitaire/c++/cmake
url = git@github.com:corrodedHash/cmake-framework.git

View File

@@ -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
View 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],
)

View File

@@ -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
View File

@@ -0,0 +1 @@
build*/

View 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)

View 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)

View 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;
};
}

View 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

View 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

View 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

View File

@@ -0,0 +1 @@
#include "board.hpp"

View 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

View 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

View File

@@ -41,30 +41,27 @@ def adjust_squares(
if not adjustment: if not adjustment:
adjustment = Adjustment(w=10, h=10) adjustment = Adjustment(w=10, h=10)
high_speed = False
def _adjustment_step(keycode: int, high_speed: bool) -> None: def _adjustment_step(keycode: int) -> None:
assert adjustment is not None assert adjustment is not None
x_keys = {104: -1, 115: +1} x_keys = {81: -1, 83: +1, 104: -10, 115: +10}
y_keys = {116: -1, 110: +1} y_keys = {82: -1, 84: +1, 116: -10, 110: +10}
w_keys = {97: -1, 117: +1} w_keys = {97: -1, 117: +1}
h_keys = {111: -1, 101: +1} h_keys = {111: -1, 101: +1}
dx_keys = {59: -1, 112: +1} dx_keys = {59: -1, 112: +1}
dy_keys = {44: -1, 46: +1} dy_keys = {44: -1, 46: +1}
high_speed_fac = 10
cur_high_speed_fac = high_speed_fac if high_speed else 1
if keycode in x_keys: if keycode in x_keys:
adjustment.x += x_keys[keycode] * cur_high_speed_fac adjustment.x += x_keys[keycode]
elif keycode in y_keys: elif keycode in y_keys:
adjustment.y += y_keys[keycode] * cur_high_speed_fac adjustment.y += y_keys[keycode]
elif keycode in w_keys: elif keycode in w_keys:
adjustment.w += w_keys[keycode] * cur_high_speed_fac adjustment.w += w_keys[keycode]
elif keycode in h_keys: elif keycode in h_keys:
adjustment.h += h_keys[keycode] * cur_high_speed_fac adjustment.h += h_keys[keycode]
elif keycode in dx_keys: elif keycode in dx_keys:
adjustment.dx += dx_keys[keycode] * cur_high_speed_fac adjustment.dx += dx_keys[keycode]
elif keycode in dy_keys: elif keycode in dy_keys:
adjustment.dy += dy_keys[keycode] * cur_high_speed_fac adjustment.dy += dy_keys[keycode]
while True: while True:
working_image = image.copy() working_image = image.copy()
@@ -78,10 +75,7 @@ def adjust_squares(
print(keycode) print(keycode)
if keycode == 27: if keycode == 27:
break break
if keycode == 229: _adjustment_step(keycode)
high_speed = not high_speed
continue
_adjustment_step(keycode, high_speed)
cv2.destroyWindow("Window") cv2.destroyWindow("Window")
return adjustment return adjustment

View File

@@ -6,7 +6,6 @@ from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
import cv2 import cv2
import numpy as np import numpy as np
import json
from ..board import Board, Card, NumberCard, SpecialCard from ..board import Board, Card, NumberCard, SpecialCard
from . import adjustment, card_finder from . import adjustment, card_finder
@@ -226,46 +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
def field_card_to_str(card: Card):
if card == SpecialCard.Hua:
return "Hua"
if isinstance(card, SpecialCard):
return {"Special": card.name}
elif isinstance(card, NumberCard):
return {"Number": {"value": card.number, "suit": card.suit.name}}
def bunker_card_to_str(card: Union[Tuple[SpecialCard, int], Optional[Card]]):
if card is None:
return "Empty"
if isinstance(card, tuple):
return {"Blocked": card[0].name}
return {"Stashed": field_card_to_str(card)}
def goal_card_to_str(card: Optional[NumberCard]):
if card is None:
return None
return {"value": card.number, "suit": card.suit.name}
def parse_to_json(image: np.ndarray, conf: Configuration) -> str:
field = parse_field(image, conf)
flower_gone = parse_hua(image, conf)
bunker = parse_bunker(image, conf)
goal = parse_goal(image, conf)
mystruct = {
"field": [[field_card_to_str(card) for card in row] for row in field],
"hua_set": flower_gone,
"bunker": [bunker_card_to_str(card) for card in bunker],
"goal": [goal_card_to_str(card) for card in goal],
}
return json.dumps(mystruct)

View File

@@ -99,68 +99,12 @@ def _save_adjustments(zip_file: zipfile.ZipFile, conf: Configuration) -> None:
adjustments = {} adjustments = {}
adjustments[FIELD_ADJUSTMENT_KEY] = dataclasses.asdict(conf.field_adjustment) adjustments[FIELD_ADJUSTMENT_KEY] = dataclasses.asdict(conf.field_adjustment)
adjustments[BORDER_ADJUSTMENT_KEY] = dataclasses.asdict(conf.border_adjustment) adjustments[BORDER_ADJUSTMENT_KEY] = dataclasses.asdict(conf.border_adjustment)
adjustments[GOAL_ADJUSTMENT_KEY] = dataclasses.asdict(conf.goal_adjustment)
adjustments[BUNKER_ADJUSTMENT_KEY] = dataclasses.asdict(conf.bunker_adjustment)
adjustments[HUA_ADJUSTMENT_KEY] = dataclasses.asdict(conf.hua_adjustment)
adjustments[SPECIAL_BUTTON_ADJUSTMENT_KEY] = dataclasses.asdict(
conf.special_button_adjustment
)
print(adjustments)
zip_file.writestr( zip_file.writestr(
ADJUSTMENT_FILE_NAME, json.dumps(adjustments), ADJUSTMENT_FILE_NAME, json.dumps(adjustment),
) )
def _save_special_images(zip_file: zipfile.ZipFile, conf: Configuration) -> None:
def _save_special_image(
zip_file: zipfile.ZipFile, images: List[np.ndarray], directory: str
) -> None:
for index, image in enumerate(images):
fd, myfile = tempfile.mkstemp(suffix=f".{PICTURE_EXTENSION}")
cv2.imwrite(myfile, image)
file_name = ""
zip_file.write(
myfile, arcname=f"{directory}/{index:03}.{PICTURE_EXTENSION}"
)
_save_special_image(zip_file, conf.card_border, CARD_BORDER_DIRECTORY)
_save_special_image(zip_file, conf.empty_card, EMPTY_CARD_DIRECTORY)
_save_special_image(zip_file, conf.green_card, GREEN_CARD_DIRECTORY)
_save_special_image(zip_file, conf.card_back, CARD_BACK_DIRECTORY)
def _generate_special_button_filename(
state: ButtonState, special_card: board.SpecialCard
) -> str:
state_char_map = {
ButtonState.normal: "n",
ButtonState.greyed: "g",
ButtonState.shiny: "s",
}
special_card_char_map = {
board.SpecialCard.Fa: "f",
board.SpecialCard.Zhong: "z",
board.SpecialCard.Bai: "b",
}
return f"{state_char_map[state]}{special_card_char_map[special_card]}"
def _save_special_button_images(
zip_file: zipfile.ZipFile,
special_button_images: List[Tuple[ButtonState, board.SpecialCard, np.ndarray]],
):
for index, (state, card, image) in enumerate(special_button_images):
fd, myfile = tempfile.mkstemp(suffix=f".{PICTURE_EXTENSION}")
cv2.imwrite(myfile, image)
file_name = ""
zip_file.write(
myfile,
arcname=f"{SPECIAL_BUTTON_DIRECTORY}/"
f"{_generate_special_button_filename(state,card)}"
f"{index:03}.{PICTURE_EXTENSION}",
)
def save(conf: Configuration, filename: str) -> None: def save(conf: Configuration, filename: str) -> None:
"""Save configuration to zip archive""" """Save configuration to zip archive"""
zip_stream = io.BytesIO() zip_stream = io.BytesIO()
@@ -168,8 +112,7 @@ def save(conf: Configuration, filename: str) -> None:
with zipfile.ZipFile(zip_stream, "w") as zip_file: with zipfile.ZipFile(zip_stream, "w") as zip_file:
_save_adjustments(zip_file, conf) _save_adjustments(zip_file, conf)
_save_catalogue(zip_file, conf.catalogue) _save_catalogue(zip_file, conf.catalogue)
_save_special_images(zip_file, conf) # TODO: Save card_borders and emtpy_card and green_card and special_buttons and card_back
_save_special_button_images(zip_file, conf.special_buttons)
with open(filename, "wb") as zip_archive: with open(filename, "wb") as zip_archive:
zip_archive.write(zip_stream.getvalue()) zip_archive.write(zip_stream.getvalue())

View File

@@ -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):

View File

@@ -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),

View File

@@ -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)

View File

@@ -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:

View File

@@ -1,78 +0,0 @@
import argparse
import copy
import dataclasses
import json
import os
import cv2
import numpy as np
import shenzhen_solitaire.card_detection.adjustment as adjustment
import shenzhen_solitaire.card_detection.card_finder as card_finder
import shenzhen_solitaire.card_detection.configuration as configuration
from shenzhen_solitaire.card_detection.configuration import Configuration
def main() -> None:
"""Generate a configuration"""
parser = argparse.ArgumentParser(
description="Calibrate to fit all symbols. "
"Ideally use a screenshot with cards in the bunker, "
"in the goal and also with a killed hua card"
)
parser.add_argument(
"screenshot_path",
metavar="screenshot_path",
type=str,
help="Path to the screenshot",
)
parser.add_argument(
"--config",
metavar="config_path",
type=str,
default="test_config.zip",
help="Config path, either merge or write new",
)
args = parser.parse_args()
print(args.screenshot_path)
image = cv2.imread(args.screenshot_path)
if os.path.exists(args.config):
conf = configuration.load(args.config)
else:
conf = Configuration()
print("Field cards")
conf.field_adjustment = adjustment.adjust_squares(
image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment)
)
print("Field borders")
border_adjustment = adjustment.adjust_squares(
image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment)
)
conf.bunker_adjustment.w = conf.field_adjustment.w
conf.bunker_adjustment.h = conf.field_adjustment.h
print("Bunker cards")
bunker_adjustment = adjustment.adjust_squares(
image, count_x=3, count_y=1, adjustment=copy.deepcopy(conf.bunker_adjustment)
)
conf.goal_adjustment.w = conf.field_adjustment.w
conf.goal_adjustment.h = conf.field_adjustment.h
print("Goal cards")
goal_adjustment = adjustment.adjust_squares(
image, count_x=3, count_y=1, adjustment=copy.deepcopy(conf.goal_adjustment)
)
conf.hua_adjustment.w = conf.field_adjustment.w
conf.hua_adjustment.h = conf.field_adjustment.h
print("Hua card")
hua_adjustment = adjustment.adjust_squares(
image, count_x=1, count_y=1, adjustment=copy.deepcopy(conf.hua_adjustment)
)
configuration.save(conf, args.config)
if __name__ == "__main__":
main()

View File

@@ -8,19 +8,11 @@ import numpy as np
import shenzhen_solitaire.card_detection.adjustment as adjustment import shenzhen_solitaire.card_detection.adjustment as adjustment
import shenzhen_solitaire.card_detection.card_finder as card_finder import shenzhen_solitaire.card_detection.card_finder as card_finder
from shenzhen_solitaire.card_detection.configuration import Configuration from shenzhen_solitaire.card_detection.configuration import Configuration
import argparse
def main() -> None: def main() -> None:
"""Generate a configuration""" """Generate a configuration"""
image = cv2.imread("pictures/20190809172213_1.jpg")
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('screenshot_path', metavar='screenshot_path', type=str,
help='Path to the screenshot')
args = parser.parse_args()
print(args.screenshot_path)
image = cv2.imread(args.screenshot_path)
border_adjustment = adjustment.adjust_squares(image, count_x=8, count_y=13) border_adjustment = adjustment.adjust_squares(image, count_x=8, count_y=13)
border_square_pos = adjustment.adjust_squares( border_square_pos = adjustment.adjust_squares(

View File

@@ -1,54 +0,0 @@
import argparse
import cv2
import numpy as np
from shenzhen_solitaire.card_detection import configuration, adjustment, card_finder
from shenzhen_solitaire.card_detection.configuration import Configuration
def main() -> None:
"""Generate a configuration"""
parser = argparse.ArgumentParser(
description="Generate pictures for symbols. "
"Requires screenshot of field with no moved cards, "
"so 8 columns of 5 cards each"
)
parser.add_argument(
"screenshot_path",
metavar="screenshot_path",
type=str,
help="Path to the screenshot",
)
parser.add_argument(
"--conf",
dest="config_path",
type=str,
default="config.zip",
help="Path to existing config to be merged, or new config",
)
args = parser.parse_args()
print(args.screenshot_path)
image = cv2.imread(args.screenshot_path)
conf = configuration.load(args.config)
squares = card_finder.get_field_squares(image, conf.field_adjustment, 5, 8)
catalogue = card_finder.catalogue_cards(squares)
conf.card_border.extend(
card_finder.get_field_squares(image, conf.border_adjustment, 1, 1)
)
conf.green_card.extend(
card_finder.get_field_squares(image, conf.bunker_adjustment, 1, 3)
)
conf.green_card.extend(
card_finder.get_field_squares(image, conf.goal_adjustment, 1, 3)
)
conf.green_card.extend(
card_finder.get_field_squares(image, conf.hua_adjustment, 1, 1)
)
conf.catalogue.extend(catalogue)
configuration.save(conf, args.config_path)
if __name__ == "__main__":
main()

View File

@@ -1,42 +1,15 @@
import argparse
import cv2 import cv2
import numpy as np import numpy as np
from shenzhen_solitaire.card_detection import configuration, adjustment, card_finder import shenzhen_solitaire.card_detection.configuration as configuration
from shenzhen_solitaire.card_detection.configuration import Configuration
def main() -> None: def main() -> None:
"""Generate a configuration""" """Generate a configuration"""
parser = argparse.ArgumentParser( image = cv2.imread("pictures/20190809172213_1.jpg")
description="Generate pictures for symbols, "
"requires screenshot of field with no moved cards, "
"so 8 columns of 5 cards each"
)
parser.add_argument(
"screenshot_path",
metavar="screenshot_path",
type=str,
help="Path to the screenshot",
)
parser.add_argument(
"--conf",
dest="config_path",
type=str,
default="config.zip",
help="Path to existing config to be merged, or new config",
)
args = parser.parse_args() generated_config = configuration.generate(image)
print(args.screenshot_path) configuration.save(generated_config, "test_config.zip")
image = cv2.imread(args.screenshot_path)
adj = adjustment.adjust_field(image)
squares = card_finder.get_field_squares(image, adj, 5, 8)
catalogue = card_finder.catalogue_cards(squares)
generated_config = Configuration(field_adjustment=adj, catalogue=catalogue, meta={})
configuration.save(generated_config, args.config_path)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,19 +0,0 @@
from shenzhen_solitaire.card_detection.board_parser import parse_to_json
import shenzhen_solitaire.card_detection.configuration as configuration
import cv2
import sys
def main() -> None:
if len(sys.argv) < 2:
print("Give filename pls")
return
image = cv2.imread(str(sys.argv[1]))
conf = configuration.load("test_config.zip")
print(parse_to_json(image, conf))
if __name__ == "__main__":
main()