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

1
.gitignore vendored
View File

@@ -1 +1,2 @@
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.nosetestsEnabled": false,
"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
def __init__(self) -> None:
self.field: List[List[Card]] = [[]] * Board.MAX_COLUMN_SIZE
self.bunker: List[Union[Tuple[SpecialCard, int], Optional[Card]]] = [None] * 3
self.goal: List[Optional[NumberCard]] = [None] * 3
self.flower_gone: bool = False
self._field: List[List[Card]] = [[]] * Board.MAX_COLUMN_SIZE
self._bunker: List[Union[Tuple[SpecialCard, int], Optional[Card]]] = [None] * 3
self._goal: List[Optional[NumberCard]] = [None] * 3
self._flower_gone: bool = False
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:
return card.number
else:
return 0
def setField(self, field: List[List[Card]]) -> None:
assert len(field) == 8
self._field = field
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:
return index
else:
return self.goal.index(None)
return self._goal.index(None)
def setGoal(self, suit: NumberCard.Suit, value: int) -> None:
assert len(self.goal) == 3
assert len(self._goal) == 3
assert 0 <= value
assert value <= 9
if value == 0:
self.goal[self.getGoalId(suit)] = None
self._goal[self.getGoalId(suit)] = None
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:
self.setGoal(suit, self.getGoal(suit) + 1)
def solved(self) -> bool:
"""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
if any(not isinstance(x, tuple) for x in self.bunker):
if any(not isinstance(x, tuple) for x in self._bunker):
return False
if not self.flower_gone:
if not self._flower_gone:
return False
assert all(not x for x in self.field)
assert all(not x for x in self._field)
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
def state_identifier(self) -> int:
"""Returns a unique identifier to represent the board state"""
result: int = 0
for card in self.bunker:
for card in self._bunker:
result <<= 2
if isinstance(card, tuple):
result |= 0
@@ -119,12 +129,12 @@ class Board:
result |= card.identifier()
result <<= 1
if self.flower_gone:
if self._flower_gone:
result |= 1
assert len(self.goal) == 3
assert len(self._goal) == 3
suit_sequence = list(NumberCard.Suit)
for card in self.goal:
for card in self._goal:
result <<= 5
if card is None:
result |= len(suit_sequence) * 10
@@ -134,12 +144,12 @@ class Board:
# Max stack size is 13
# (4 random cards from the start, plus a stack from 9 to 1)
# So 4 bits are sufficient
for stack in self.field:
for stack in self._field:
assert len(stack) == len(stack) & 0b1111
result <<= 4
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 |= field_card.identifier()
@@ -159,12 +169,12 @@ class Board:
SpecialCard.Hua: 0,
}
if self.flower_gone:
if self._flower_gone == True:
special_cards[SpecialCard.Hua] += 1
for card in itertools.chain(
self.bunker,
itertools.chain.from_iterable(stack for stack in self.field if stack),
self._bunker,
itertools.chain.from_iterable(stack for stack in self._field if stack),
):
if isinstance(card, tuple):
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:
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
x_keys = {104: -1, 115: +1}
y_keys = {116: -1, 110: +1}
x_keys = {81: -1, 83: +1, 104: -10, 115: +10}
y_keys = {82: -1, 84: +1, 116: -10, 110: +10}
w_keys = {97: -1, 117: +1}
h_keys = {111: -1, 101: +1}
dx_keys = {59: -1, 112: +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:
adjustment.x += x_keys[keycode] * cur_high_speed_fac
adjustment.x += x_keys[keycode]
elif keycode in y_keys:
adjustment.y += y_keys[keycode] * cur_high_speed_fac
adjustment.y += y_keys[keycode]
elif keycode in w_keys:
adjustment.w += w_keys[keycode] * cur_high_speed_fac
adjustment.w += w_keys[keycode]
elif keycode in h_keys:
adjustment.h += h_keys[keycode] * cur_high_speed_fac
adjustment.h += h_keys[keycode]
elif keycode in dx_keys:
adjustment.dx += dx_keys[keycode] * cur_high_speed_fac
adjustment.dx += dx_keys[keycode]
elif keycode in dy_keys:
adjustment.dy += dy_keys[keycode] * cur_high_speed_fac
adjustment.dy += dy_keys[keycode]
while True:
working_image = image.copy()
@@ -78,10 +75,7 @@ def adjust_squares(
print(keycode)
if keycode == 27:
break
if keycode == 229:
high_speed = not high_speed
continue
_adjustment_step(keycode, high_speed)
_adjustment_step(keycode)
cv2.destroyWindow("Window")
return adjustment

View File

@@ -6,7 +6,6 @@ from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
import cv2
import numpy as np
import json
from ..board import Board, Card, NumberCard, SpecialCard
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:
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.bunker = parse_bunker(image, conf)
result.goal = parse_goal(image, conf)
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[FIELD_ADJUSTMENT_KEY] = dataclasses.asdict(conf.field_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(
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:
"""Save configuration to zip archive"""
zip_stream = io.BytesIO()
@@ -168,8 +112,7 @@ def save(conf: Configuration, filename: str) -> None:
with zipfile.ZipFile(zip_stream, "w") as zip_file:
_save_adjustments(zip_file, conf)
_save_catalogue(zip_file, conf.catalogue)
_save_special_images(zip_file, conf)
_save_special_button_images(zip_file, conf.special_buttons)
# TODO: Save card_borders and emtpy_card and green_card and special_buttons and card_back
with open(filename, "wb") as zip_archive:
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'"""
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.field[dest].extend(self.cards)
action_board.getField()[source] = action_board.getField()[source][: -len(self.cards)]
action_board.getField()[dest].extend(self.cards)
def _apply(self, action_board: board.Board) -> None:
"""Do action"""
if action_board.field[self.destination_id]:
dest_card = action_board.field[self.destination_id][-1]
if action_board.getField()[self.destination_id]:
dest_card = action_board.getField()[self.destination_id][-1]
if not all(isinstance(x, board.NumberCard) for x in self.cards):
raise AssertionError()
if not isinstance(dest_card, board.NumberCard):

View File

@@ -9,7 +9,7 @@ def possible_huakill_action(
search_board: board.Board,
) -> Iterator[board_actions.HuaKillAction]:
"""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:
yield board_actions.HuaKillAction(
source_field_id=index, source_field_row_index=len(stack) - 1
@@ -25,17 +25,17 @@ def possible_dragonkill_actions(
board.SpecialCard.Fa,
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 = []
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)
possible_dragons = new_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 = [
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:
continue
@@ -44,7 +44,7 @@ def possible_dragonkill_actions(
destination_bunker_id = bunker_dragons[0]
else:
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]
source_stacks = [(board.Position.Bunker, i) for i in bunker_dragons]
@@ -61,13 +61,13 @@ def possible_bunkerize_actions(
search_board: board.Board,
) -> Iterator[board_actions.BunkerizeAction]:
"""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:
return
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:
continue
yield board_actions.BunkerizeAction(
@@ -85,11 +85,11 @@ def possible_debunkerize_actions(
"""Enumerates all possible card moves from the bunker to the field"""
bunker_number_cards = [
(i, x)
for i, x in enumerate(search_board.bunker)
for i, x in enumerate(search_board.getBunker())
if isinstance(x, board.NumberCard)
]
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:
continue
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"""
field_cards = [
(board.Position.Field, index, stack[-1])
for index, stack in enumerate(search_board.field)
for index, stack in enumerate(search_board.getField())
if stack
]
bunker_cards = [
(board.Position.Bunker, index, card)
for index, card in enumerate(search_board.bunker)
for index, card in enumerate(search_board.getBunker())
]
top_cards = [
x for x in field_cards + bunker_cards if isinstance(x[2], board.NumberCard)
@@ -129,6 +129,7 @@ def possible_goal_move_actions(
result = []
for source, index, card in top_cards:
assert isinstance(card, board.NumberCard)
obvious = all(
search_board.getGoal(other_suit) >= card.number - 2
for other_suit in set(board.NumberCard.Suit) - {card.suit}
@@ -137,7 +138,7 @@ def possible_goal_move_actions(
board_actions.GoalAction(
card=card,
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
else None,
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]]:
"""Returns all cards on one stack that can be moved at once"""
result: List[List[board.Card]] = []
for stack in search_board.field:
for stack in search_board.getField():
result.append([])
if not stack:
continue
@@ -194,13 +195,13 @@ def possible_field_move_actions(
)
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:
continue
if destination_stack:
if not _can_stack(destination_stack[-1], source_substack[0]):
continue
elif len(source_substack) == len(search_board.field[source_index]):
elif len(source_substack) == len(search_board.getField()[source_index]):
continue
elif first_empty_field_id == -1:
first_empty_field_id = destination_index
@@ -209,7 +210,7 @@ def possible_field_move_actions(
yield board_actions.MoveAction(
cards=source_substack,
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),
destination_id=destination_index,
destination_row_index=len(destination_stack),

View File

@@ -3,8 +3,8 @@ from shenzhen_solitaire.board import Board, NumberCard, SpecialCard
Suit = NumberCard.Suit
TEST_BOARD = Board()
TEST_BOARD.field[0] = [
_TEST_BOARD_FIELDS = [None] * 8
_TEST_BOARD_FIELDS[0] = [
SpecialCard.Fa,
NumberCard(NumberCard.Suit.Black, 8),
SpecialCard.Bai,
@@ -12,7 +12,7 @@ TEST_BOARD.field[0] = [
SpecialCard.Zhong,
]
TEST_BOARD.field[1] = [
_TEST_BOARD_FIELDS[1] = [
NumberCard(NumberCard.Suit.Red, 9),
SpecialCard.Zhong,
SpecialCard.Zhong,
@@ -20,7 +20,7 @@ TEST_BOARD.field[1] = [
NumberCard(NumberCard.Suit.Black, 3),
]
TEST_BOARD.field[2] = [
_TEST_BOARD_FIELDS[2] = [
SpecialCard.Hua,
NumberCard(NumberCard.Suit.Red, 1),
NumberCard(NumberCard.Suit.Red, 4),
@@ -28,7 +28,7 @@ TEST_BOARD.field[2] = [
NumberCard(NumberCard.Suit.Red, 6),
]
TEST_BOARD.field[3] = [
_TEST_BOARD_FIELDS[3] = [
SpecialCard.Bai,
SpecialCard.Zhong,
NumberCard(NumberCard.Suit.Red, 3),
@@ -36,7 +36,7 @@ TEST_BOARD.field[3] = [
NumberCard(NumberCard.Suit.Green, 6),
]
TEST_BOARD.field[4] = [
_TEST_BOARD_FIELDS[4] = [
NumberCard(NumberCard.Suit.Green, 7),
NumberCard(NumberCard.Suit.Green, 4),
NumberCard(NumberCard.Suit.Red, 5),
@@ -44,7 +44,7 @@ TEST_BOARD.field[4] = [
NumberCard(NumberCard.Suit.Black, 6),
]
TEST_BOARD.field[5] = [
_TEST_BOARD_FIELDS[5] = [
NumberCard(NumberCard.Suit.Green, 3),
SpecialCard.Bai,
SpecialCard.Fa,
@@ -52,7 +52,7 @@ TEST_BOARD.field[5] = [
NumberCard(NumberCard.Suit.Black, 5),
]
TEST_BOARD.field[6] = [
_TEST_BOARD_FIELDS[6] = [
SpecialCard.Fa,
NumberCard(NumberCard.Suit.Green, 9),
NumberCard(NumberCard.Suit.Green, 2),
@@ -60,16 +60,18 @@ TEST_BOARD.field[6] = [
NumberCard(NumberCard.Suit.Red, 8),
]
TEST_BOARD.field[7] = [
_TEST_BOARD_FIELDS[7] = [
SpecialCard.Bai,
NumberCard(NumberCard.Suit.Red, 2),
SpecialCard.Fa,
NumberCard(NumberCard.Suit.Black, 1),
NumberCard(NumberCard.Suit.Green, 8),
]
TEST_BOARD = Board()
TEST_BOARD.setField(_TEST_BOARD_FIELDS)
B20190809172206_1 = Board()
B20190809172206_1.field[0] = [
_B20190809172206_1_FIELDS = [None] * 8
_B20190809172206_1_FIELDS[0] = [
NumberCard(Suit.Green, 6),
NumberCard(Suit.Green, 5),
NumberCard(Suit.Red, 4),
@@ -77,7 +79,7 @@ B20190809172206_1.field[0] = [
SpecialCard.Fa,
]
B20190809172206_1.field[1] = [
_B20190809172206_1_FIELDS[1] = [
NumberCard(Suit.Black, 8),
NumberCard(Suit.Black, 6),
SpecialCard.Zhong,
@@ -85,33 +87,33 @@ B20190809172206_1.field[1] = [
NumberCard(Suit.Green, 7),
]
B20190809172206_1.field[2] = [
_B20190809172206_1_FIELDS[2] = [
SpecialCard.Zhong,
NumberCard(Suit.Black, 4),
NumberCard(Suit.Green, 2),
SpecialCard.Bai,
SpecialCard.Zhong,
]
B20190809172206_1.field[3] = [
_B20190809172206_1_FIELDS[3] = [
NumberCard(Suit.Green, 1),
NumberCard(Suit.Green, 3),
NumberCard(Suit.Black, 5),
SpecialCard.Fa,
SpecialCard.Fa,
]
B20190809172206_1.field[4] = [
_B20190809172206_1_FIELDS[4] = [
NumberCard(Suit.Red, 8),
SpecialCard.Zhong,
NumberCard(Suit.Red, 7),
]
B20190809172206_1.field[5] = [
_B20190809172206_1_FIELDS[5] = [
SpecialCard.Fa,
SpecialCard.Bai,
NumberCard(Suit.Red, 2),
SpecialCard.Hua,
SpecialCard.Bai,
]
B20190809172206_1.field[6] = [
_B20190809172206_1_FIELDS[6] = [
NumberCard(Suit.Black, 2),
NumberCard(Suit.Green, 8),
NumberCard(Suit.Black, 7),
@@ -119,10 +121,12 @@ B20190809172206_1.field[6] = [
NumberCard(Suit.Red, 9),
]
B20190809172206_1.field[7] = [
_B20190809172206_1_FIELDS[7] = [
NumberCard(Suit.Red, 3),
NumberCard(Suit.Black, 3),
NumberCard(Suit.Green, 9),
NumberCard(Suit.Red, 5),
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")
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)
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.card_finder as card_finder
from shenzhen_solitaire.card_detection.configuration import Configuration
import argparse
def main() -> None:
"""Generate a configuration"""
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)
image = cv2.imread("pictures/20190809172213_1.jpg")
border_adjustment = adjustment.adjust_squares(image, count_x=8, count_y=13)
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 numpy as np
from shenzhen_solitaire.card_detection import configuration, adjustment, card_finder
from shenzhen_solitaire.card_detection.configuration import Configuration
import shenzhen_solitaire.card_detection.configuration as 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",
)
image = cv2.imread("pictures/20190809172213_1.jpg")
args = parser.parse_args()
print(args.screenshot_path)
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)
generated_config = configuration.generate(image)
configuration.save(generated_config, "test_config.zip")
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()