Added virtenv
This commit is contained in:
12
Pipfile
Normal file
12
Pipfile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
pyautogui = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.8"
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
"""Contains parse_board function"""
|
"""Contains parse_board function"""
|
||||||
|
|
||||||
|
import copy
|
||||||
import itertools
|
import itertools
|
||||||
from typing import Any, Iterable, List, Optional, Tuple, Union, Dict
|
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from ..board import Board, Card, NumberCard, SpecialCard
|
from ..board import Board, Card, NumberCard, SpecialCard
|
||||||
from . import card_finder
|
from . import adjustment, card_finder
|
||||||
from .configuration import Configuration
|
from .configuration import Configuration, ButtonState
|
||||||
|
|
||||||
|
|
||||||
def grouper(
|
def grouper(
|
||||||
@@ -19,20 +20,27 @@ def grouper(
|
|||||||
return itertools.zip_longest(*args, fillvalue=fillvalue)
|
return itertools.zip_longest(*args, fillvalue=fillvalue)
|
||||||
|
|
||||||
|
|
||||||
|
def fake_adjustment(adj: adjustment.Adjustment) -> adjustment.Adjustment:
|
||||||
|
result = copy.deepcopy(adj)
|
||||||
|
result.x -= 5
|
||||||
|
result.y -= 5
|
||||||
|
result.h += 10
|
||||||
|
result.w += 10
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_field_square_iterator(
|
def get_field_square_iterator(
|
||||||
image: np.ndarray, conf: Configuration, row_count: int, column_count: int
|
image: np.ndarray, conf: Configuration, row_count: int, column_count: int
|
||||||
) -> Iterable[Tuple[np.ndarray, np.ndarray]]:
|
) -> Iterable[Tuple[np.ndarray, np.ndarray]]:
|
||||||
"""Return iterator for both the square, as well as the matching card border"""
|
"""Return iterator for both the square, as well as the matching card border"""
|
||||||
fake_adjustments = conf.field_adjustment
|
my_adj = fake_adjustment(conf.field_adjustment)
|
||||||
fake_adjustments.x -= 5
|
my_border_adj = fake_adjustment(conf.border_adjustment)
|
||||||
fake_adjustments.y -= 5
|
|
||||||
fake_adjustments.h += 10
|
|
||||||
fake_adjustments.w += 10
|
|
||||||
squares = card_finder.get_field_squares(
|
squares = card_finder.get_field_squares(
|
||||||
image, fake_adjustments, count_x=row_count, count_y=column_count
|
image, my_adj, count_x=row_count, count_y=column_count
|
||||||
)
|
)
|
||||||
border_squares = card_finder.get_field_squares(
|
border_squares = card_finder.get_field_squares(
|
||||||
image, conf.border_adjustment, count_x=row_count, count_y=column_count
|
image, my_border_adj, count_x=row_count, count_y=column_count
|
||||||
)
|
)
|
||||||
grouped_squares = grouper(squares, row_count)
|
grouped_squares = grouper(squares, row_count)
|
||||||
grouped_border_squares = grouper(border_squares, row_count)
|
grouped_border_squares = grouper(border_squares, row_count)
|
||||||
@@ -96,23 +104,132 @@ def parse_field(image: np.ndarray, conf: Configuration) -> List[List[Card]]:
|
|||||||
|
|
||||||
def parse_hua(image: np.ndarray, conf: Configuration) -> bool:
|
def parse_hua(image: np.ndarray, conf: Configuration) -> bool:
|
||||||
"""Return true if hua is in the hua spot, false if hua spot is empty"""
|
"""Return true if hua is in the hua spot, false if hua spot is empty"""
|
||||||
raise NotImplementedError()
|
my_hua_adj = fake_adjustment(conf.hua_adjustment)
|
||||||
|
hua_square = card_finder.get_field_squares(image, my_hua_adj, count_x=1, count_y=1)[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
hua_templates = [
|
||||||
|
image for image, card_type in conf.catalogue if card_type == SpecialCard.Hua
|
||||||
|
]
|
||||||
|
best_hua = max(
|
||||||
|
match_template(template=template, search_image=hua_square)
|
||||||
|
for template in hua_templates
|
||||||
|
)
|
||||||
|
best_green = max(
|
||||||
|
match_template(template=template, search_image=hua_square)
|
||||||
|
for template in conf.green_card
|
||||||
|
)
|
||||||
|
return best_hua > best_green
|
||||||
|
|
||||||
|
|
||||||
|
def parse_bunker_field(
|
||||||
|
image: np.ndarray,
|
||||||
|
green_cards: List[np.ndarray],
|
||||||
|
card_backs: List[np.ndarray],
|
||||||
|
catalogue: List[Tuple[np.ndarray, Card]],
|
||||||
|
) -> Union[Tuple[SpecialCard, int], Optional[Card]]:
|
||||||
|
|
||||||
|
best_green = max(
|
||||||
|
match_template(template=template, search_image=image)
|
||||||
|
for template in green_cards
|
||||||
|
)
|
||||||
|
best_back = max(
|
||||||
|
match_template(template=template, search_image=image) for template in card_backs
|
||||||
|
)
|
||||||
|
|
||||||
|
best_card_value, best_card_name = max(
|
||||||
|
((match_template(template, image), name) for template, name in catalogue),
|
||||||
|
key=lambda x: x[0],
|
||||||
|
)
|
||||||
|
|
||||||
|
return max(
|
||||||
|
[
|
||||||
|
(best_green, None),
|
||||||
|
(best_back, (SpecialCard.Hua, 0)),
|
||||||
|
(best_card_value, best_card_name),
|
||||||
|
],
|
||||||
|
key=lambda x: x[0],
|
||||||
|
)[1]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_special_button(
|
||||||
|
image: np.ndarray,
|
||||||
|
position: SpecialCard,
|
||||||
|
buttons: List[Tuple[ButtonState, SpecialCard, np.ndarray]],
|
||||||
|
) -> ButtonState:
|
||||||
|
"""Return true if special button is greyed out, e.g. this dragon card is removed from the field"""
|
||||||
|
square_fits = [
|
||||||
|
(match_template(template, image), state, name)
|
||||||
|
for state, name, template in buttons
|
||||||
|
]
|
||||||
|
best_state, best_name = max(square_fits, key=lambda x: x[0])[1:]
|
||||||
|
assert best_name == position
|
||||||
|
return best_state
|
||||||
|
|
||||||
|
|
||||||
def parse_bunker(
|
def parse_bunker(
|
||||||
image: np.ndarray, conf: Configuration
|
image: np.ndarray, conf: Configuration
|
||||||
) -> List[Union[Tuple[SpecialCard, int], Optional[Card]]]:
|
) -> List[Union[Tuple[SpecialCard, int], Optional[Card]]]:
|
||||||
raise NotImplementedError()
|
bunker_squares = card_finder.get_field_squares(
|
||||||
|
image, fake_adjustment(conf.bunker_adjustment), count_x=1, count_y=3
|
||||||
|
)
|
||||||
|
button_squares = card_finder.get_field_squares(
|
||||||
|
image, fake_adjustment(conf.special_button_adjustment), count_x=3, count_y=1
|
||||||
|
)
|
||||||
|
dragon_sequence = [SpecialCard.Zhong, SpecialCard.Fa, SpecialCard.Bai]
|
||||||
|
dragons = [
|
||||||
|
card_type
|
||||||
|
for dragon_image, card_type in zip(button_squares, dragon_sequence)
|
||||||
|
if parse_special_button(dragon_image, card_type, conf.special_buttons)
|
||||||
|
== ButtonState.greyed
|
||||||
|
]
|
||||||
|
dragon_iter = iter(dragons)
|
||||||
|
matches = [
|
||||||
|
parse_bunker_field(square, conf.green_card, conf.card_back, conf.catalogue)
|
||||||
|
for square in bunker_squares
|
||||||
|
]
|
||||||
|
matches = [(next(dragon_iter), 0) if isinstance(x, tuple) else x for x in matches]
|
||||||
|
assert next(dragon_iter, None) is None
|
||||||
|
return matches
|
||||||
|
|
||||||
|
|
||||||
|
def parse_goal_field(
|
||||||
|
image: np.ndarray,
|
||||||
|
catalogue: List[Tuple[np.ndarray, Card]],
|
||||||
|
green_cards: List[np.ndarray],
|
||||||
|
) -> Optional[NumberCard]:
|
||||||
|
square_fits = [
|
||||||
|
(match_template(template, image), name) for template, name in catalogue
|
||||||
|
]
|
||||||
|
best_card_value, best_card_name = max(square_fits, key=lambda x: x[0])
|
||||||
|
|
||||||
|
best_green_value = max(match_template(template, image) for template in green_cards)
|
||||||
|
if best_green_value > best_card_value:
|
||||||
|
return None
|
||||||
|
|
||||||
|
assert isinstance(best_card_name, NumberCard)
|
||||||
|
return best_card_name
|
||||||
|
|
||||||
|
|
||||||
def parse_goal(image: np.ndarray, conf: Configuration) -> Dict[NumberCard.Suit, int]:
|
def parse_goal(image: np.ndarray, conf: Configuration) -> Dict[NumberCard.Suit, int]:
|
||||||
raise NotImplementedError()
|
goal_squares = card_finder.get_field_squares(
|
||||||
|
image, fake_adjustment(conf.goal_adjustment), count_x=1, count_y=3
|
||||||
|
)
|
||||||
|
goal_list = [
|
||||||
|
parse_goal_field(square, conf.catalogue, conf.green_card)
|
||||||
|
for square in goal_squares
|
||||||
|
]
|
||||||
|
base_goal_dict = {suit: 0 for suit in NumberCard.Suit}
|
||||||
|
base_goal_dict.update(
|
||||||
|
{x.suit: x.number for x in (x for x in goal_list if x is not None)}
|
||||||
|
)
|
||||||
|
return base_goal_dict
|
||||||
|
|
||||||
|
|
||||||
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.field = parse_field(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)
|
result.goal = parse_goal(image, conf)
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
"""Contains function to manually test the visual detection of a board"""
|
"""Contains function to manually test the visual detection of a board"""
|
||||||
|
|
||||||
|
import copy
|
||||||
import unittest
|
import unittest
|
||||||
|
from typing import List, Tuple, Union
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from shenzhen_solitaire import board
|
|
||||||
from shenzhen_solitaire.card_detection import adjustment, board_parser
|
|
||||||
import shenzhen_solitaire.card_detection.configuration as configuration
|
import shenzhen_solitaire.card_detection.configuration as configuration
|
||||||
|
from shenzhen_solitaire import board
|
||||||
|
from shenzhen_solitaire.board import Card, NumberCard, SpecialCard
|
||||||
|
from shenzhen_solitaire.card_detection import adjustment, board_parser
|
||||||
|
|
||||||
from . import boards
|
from . import boards
|
||||||
|
|
||||||
|
|
||||||
@@ -21,3 +25,84 @@ class CardDetectionTest(unittest.TestCase):
|
|||||||
|
|
||||||
for correct_row, my_row in zip(boards.B20190809172206_1.field, my_board.field):
|
for correct_row, my_row in zip(boards.B20190809172206_1.field, my_board.field):
|
||||||
self.assertListEqual(correct_row, my_row)
|
self.assertListEqual(correct_row, my_row)
|
||||||
|
|
||||||
|
def test_hua_detection(self) -> None:
|
||||||
|
"""Read a board and check if it can detect if the flower is gone"""
|
||||||
|
loaded_config = configuration.load("test_config.zip")
|
||||||
|
imagenames = [
|
||||||
|
("BaiBlack", False),
|
||||||
|
("BaiShiny", True),
|
||||||
|
("BunkerCards", True),
|
||||||
|
("FaShiny", False),
|
||||||
|
("ZhongShiny", False),
|
||||||
|
]
|
||||||
|
for imagename, flower_gone in imagenames:
|
||||||
|
image = cv2.imread(f"pictures/specific/{imagename}.jpg")
|
||||||
|
my_board = board_parser.parse_board(image, loaded_config)
|
||||||
|
self.assertEqual(flower_gone, my_board.flower_gone)
|
||||||
|
|
||||||
|
def test_bunker_parsing(self) -> None:
|
||||||
|
loaded_config = configuration.load("test_config.zip")
|
||||||
|
imagenames: List[
|
||||||
|
Tuple[str, List[Union[Tuple[SpecialCard, int], Card, None]]]
|
||||||
|
] = [
|
||||||
|
(
|
||||||
|
"BaiBlack",
|
||||||
|
[(SpecialCard.Bai, 0), None, NumberCard(NumberCard.Suit.Green, 3)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"BaiShiny",
|
||||||
|
[(SpecialCard.Zhong, 0), SpecialCard.Bai, (SpecialCard.Fa, 0)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"BunkerCards",
|
||||||
|
[
|
||||||
|
NumberCard(NumberCard.Suit.Black, 6),
|
||||||
|
NumberCard(NumberCard.Suit.Green, 9),
|
||||||
|
NumberCard(NumberCard.Suit.Green, 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
("FaShiny", [None, NumberCard(NumberCard.Suit.Green, 6), SpecialCard.Fa]),
|
||||||
|
(
|
||||||
|
"ZhongShiny",
|
||||||
|
[
|
||||||
|
(SpecialCard.Fa, 0),
|
||||||
|
NumberCard(NumberCard.Suit.Green, 6),
|
||||||
|
SpecialCard.Zhong,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
for imagename, bunker in imagenames:
|
||||||
|
image = cv2.imread(f"pictures/specific/{imagename}.jpg")
|
||||||
|
my_board = board_parser.parse_board(image, loaded_config)
|
||||||
|
self.assertListEqual(bunker, my_board.bunker)
|
||||||
|
|
||||||
|
def test_goal_parsing(self) -> None:
|
||||||
|
loaded_config = configuration.load("test_config.zip")
|
||||||
|
imagenames: List[Tuple[str, List[NumberCard]]] = [
|
||||||
|
("BaiBlack", [NumberCard(NumberCard.Suit.Green, 2)],),
|
||||||
|
(
|
||||||
|
"BaiShiny",
|
||||||
|
[
|
||||||
|
NumberCard(NumberCard.Suit.Green, 3),
|
||||||
|
NumberCard(NumberCard.Suit.Red, 2),
|
||||||
|
NumberCard(NumberCard.Suit.Black, 3),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"BunkerCards",
|
||||||
|
[
|
||||||
|
NumberCard(NumberCard.Suit.Red, 1),
|
||||||
|
NumberCard(NumberCard.Suit.Black, 1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
("FaShiny", [NumberCard(NumberCard.Suit.Green, 2)]),
|
||||||
|
("ZhongShiny", [NumberCard(NumberCard.Suit.Green, 2)]),
|
||||||
|
]
|
||||||
|
base_goal_dict = {suit: 0 for suit in NumberCard.Suit}
|
||||||
|
for imagename, goal in imagenames:
|
||||||
|
image = cv2.imread(f"pictures/specific/{imagename}.jpg")
|
||||||
|
my_goal_dict = copy.deepcopy(base_goal_dict)
|
||||||
|
my_goal_dict.update({x.suit: x.number for x in goal})
|
||||||
|
my_board = board_parser.parse_board(image, loaded_config)
|
||||||
|
self.assertDictEqual(my_goal_dict, my_board.goal)
|
||||||
|
|||||||
Reference in New Issue
Block a user