-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
85c7de8
commit 2f49712
Showing
7 changed files
with
527 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# ---------------------------------------------------------------------------------------------------------------------- | ||
# SPDX-License-Identifier: BSD 3-Clause - | ||
# Copyright (c) 2022 Jimmy Bierenbroodspot. - | ||
# ---------------------------------------------------------------------------------------------------------------------- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# ---------------------------------------------------------------------------------------------------------------------- | ||
# SPDX-License-Identifier: BSD 3-Clause - | ||
# Copyright (c) 2022 Jimmy Bierenbroodspot. - | ||
# ---------------------------------------------------------------------------------------------------------------------- | ||
"""A mastermind algorithm that tries to use the fundamentals of bogosort to solve a game of mastermind. It pops a random | ||
combination from the pool of possible combinations and checks if it is the correct code, then repeats. This is obviously | ||
horrifically inefficient. | ||
This algorithm needs at best 1 guess and at most n^r, where n = the amount of objects and r = the sample size, guesses. | ||
Because the probability of each amount of guesses to win the game (k) is equally distributed the average amount of | ||
guesses needed by this algorithm is n^r/2. For a standard game of mastermind (n = 6 and r = 4) is this 6^4/2 = 1296/2 = | ||
648 and since this number for most algorithms is around 3 or 4 makes bogorithm orders of magnitude worse than most | ||
algorithms out there. | ||
This could be improved on by reducing the incompatible codes from the pool of possible guesses. This makes it the | ||
simple algorithm but with a random guess rather than the first pick from the pool. | ||
""" | ||
import typing | ||
import random | ||
|
||
import src.python.mastermind.mastermind as game | ||
import initialization as init | ||
|
||
# Overwrite game length to max amount of guesses. | ||
init.GAME_LENGTH = 1296 | ||
|
||
|
||
def main() -> None: | ||
game_simulation: typing.Generator[typing.Tuple[int, int, bool], game.Code, None] | ||
answer: typing.Tuple[int, int, bool] | ||
guess: game.Code | ||
game_round: int = 0 | ||
possible_combinations: typing.List[game.Code] = init.get_combinations() | ||
|
||
game_simulation = game.simulate_game( | ||
init.COLOURS, init.GAME_LENGTH, init.GAME_WIDTH | ||
) | ||
for _ in game_simulation: | ||
game_round += 1 | ||
|
||
guess = possible_combinations.pop( | ||
random.randint(0, len(possible_combinations) - 1) | ||
) | ||
answer = game_simulation.send(guess) | ||
|
||
if answer[2]: | ||
print(f"Game won in {game_round} guesses!") | ||
break | ||
else: | ||
print("Game lost.") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# ---------------------------------------------------------------------------------------------------------------------- | ||
# SPDX-License-Identifier: BSD 3-Clause - | ||
# Copyright (c) 2022 Jimmy Bierenbroodspot. - | ||
# ---------------------------------------------------------------------------------------------------------------------- | ||
"""A module containing some constants, types and functions which are commonly shared between algorithms.""" | ||
import typing | ||
import json | ||
|
||
import scripts.generate_set as generate_set | ||
import src.python.mastermind.mastermind as game | ||
|
||
# These constants are declared here because they remain the same value in the entire module since I haven't implemented | ||
# a way to customize them yet | ||
GAME_WIDTH: int = 4 | ||
GAME_LENGTH: int = 8 | ||
COLOURS: typing.Tuple[int, ...] = tuple(num for num in range(6)) | ||
|
||
# Create generic Json type for ease of use, this has no actual functionality other than showing that something is a | ||
# json serialized object. | ||
Json: typing.Generic = typing.TypeVar("Json") | ||
|
||
|
||
def get_combinations() -> Json: | ||
"""Retrieves a list of all possible combinations from combinations.json. | ||
:return: A Json serialized object with all possible combinations. | ||
""" | ||
json_io: typing.TextIO | ||
json_string: str | ||
|
||
# Generate dataset with possible combinations | ||
generate_set.main() | ||
|
||
with open("./combinations.json", "r") as json_io: | ||
json_string = json_io.read() | ||
|
||
return json.loads(json_string) | ||
|
||
|
||
def reduce( | ||
possible_combinations: Json, guess: game.Code, score: typing.Tuple[int, int] | ||
) -> Json: | ||
"""Compares the score of all combinations in possible_combinations against the given score. | ||
:param possible_combinations: A list of possible combinations. | ||
:param guess: A sequence containing integers. | ||
:param score: A tuple containing 2 integers. | ||
:return: A list | ||
""" | ||
# Using a list comprehension is absolutely useless here because game.compare_codes is a relatively expensive | ||
# function. I just really like writing comprehensions. | ||
return [ | ||
possible_combination | ||
for possible_combination in possible_combinations | ||
if score == game.compare_codes(guess, possible_combination) | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# ---------------------------------------------------------------------------------------------------------------------- | ||
# SPDX-License-Identifier: BSD 3-Clause - | ||
# Copyright (c) 2022 Jimmy Bierenbroodspot. - | ||
# ---------------------------------------------------------------------------------------------------------------------- | ||
""" | ||
Mastermind solving algorithm using B. Kooi's new strategy algorithm. | ||
This code is developed using pseudocode found in the following article: | ||
Kooi, B. (2005). Yet another mastermind strategy. ICGA Journal, 28(1), 13-20. | ||
""" | ||
import typing | ||
import collections | ||
|
||
import initialization as init | ||
import src.python.mastermind.mastermind as game | ||
|
||
|
||
def main() -> None: | ||
game_simulation: typing.Generator[typing.Tuple[int, int, bool], game.Code, None] | ||
guess: game.Code | ||
answer: typing.Tuple[int, int, bool] | ||
categories: typing.Set[game.Code] | ||
partition_counts: typing.Dict[game.Code, int] | ||
largest_category: game.Code | ||
game_round: int = 0 | ||
combinations: init.Json = init.get_combinations() | ||
|
||
game_simulation = game.simulate_game( | ||
init.COLOURS, init.GAME_LENGTH, init.GAME_WIDTH | ||
) | ||
for _ in game_simulation: | ||
game_round += 1 | ||
|
||
# Find all categories left in the combinations and count the partitions. | ||
categories = get_all_categories(combinations) | ||
partition_counts = { | ||
category: get_partition_count(combinations, category) | ||
for category in categories | ||
} | ||
# Find the largest category. | ||
largest_category = max(partition_counts, key=partition_counts.get) | ||
guess = next( | ||
combination | ||
for combination in combinations | ||
if get_category(combination) == largest_category | ||
) | ||
answer = game_simulation.send(guess) | ||
print(f"Guessed: {guess}; answer: {answer}") | ||
|
||
# Check if the game is won | ||
if answer[2]: | ||
print(f"Game won in {game_round} guesses!") | ||
break | ||
|
||
combinations = init.reduce(combinations, guess, answer[:2]) | ||
else: | ||
print("Game lost.") | ||
|
||
|
||
def get_category(combination: game.Code) -> game.Code: | ||
"""Decides the category of a combination. In most papers these categories are describes as: AAAA, AAAB, AABB, AABC | ||
and ABCD. | ||
:param combination: A Code combination of any width. | ||
:return: A list that substitutes the letters in a category for integers in such that AABC == [0, 0, 1, 2]. | ||
""" | ||
# Creates a list of values within the counter, we do not actually know to which number each value belongs. | ||
combination_counts: typing.List[int] = sorted( | ||
collections.Counter(combination).values(), reverse=True | ||
) | ||
category: game.Code = [] # This could've been a comprehension... | ||
|
||
# Appends the amount each colour (number) appears in the combination. | ||
for counter, combination_count in enumerate(combination_counts): | ||
category.extend([counter] * combination_count) | ||
|
||
return tuple(category) | ||
|
||
|
||
def get_partition_count( | ||
possible_combinations: init.Json, combination: game.Code | ||
) -> int: | ||
"""Finds the amount a given combination can be partitioned in by comparing the codes and calculating the possible | ||
answer. | ||
:param possible_combinations: A list with unique Code combinations. | ||
:param combination: A Code combination. | ||
:return: The amount of possible partitions. | ||
""" | ||
# Comprehensions are fun, aren't they? | ||
return len( | ||
collections.Counter( | ||
[ | ||
game.compare_codes(combination, possible_combination) | ||
for possible_combination in possible_combinations | ||
] | ||
) | ||
) | ||
|
||
|
||
def get_all_categories( | ||
possible_combinations: init.Json, | ||
) -> typing.Set[typing.Tuple[int]]: | ||
"""Gets all categories in a list of combinations. | ||
:param possible_combinations: A list of possible combinations. | ||
:return: A set with one of every available category. | ||
""" | ||
return { | ||
tuple(get_category(possible_combination)) | ||
for possible_combination in possible_combinations | ||
} | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# ---------------------------------------------------------------------------------------------------------------------- | ||
# SPDX-License-Identifier: BSD 3-Clause - | ||
# Copyright (c) 2022 Jimmy Bierenbroodspot. - | ||
# ---------------------------------------------------------------------------------------------------------------------- | ||
""" | ||
Mastermind solving algorithm using L. Sterling's and E. Shapiro's simple algorithm. | ||
This code is developed using code found in the following book: | ||
Sterling, L., & Shapiro, E. (1994). The art of Prolog: advanced programming techniques (2nd ed.). MIT Press. | ||
""" | ||
from __future__ import annotations | ||
import typing | ||
|
||
import src.python.mastermind.mastermind as game | ||
import initialization as init | ||
|
||
|
||
def main() -> None: | ||
game_simulation: typing.Generator[typing.Tuple[int, int, bool], game.Code, None] | ||
guess: game.Code | ||
answer: typing.Tuple[int, int, bool] | ||
game_round: int = 0 | ||
possible_combinations: init.Json = init.get_combinations() | ||
|
||
game_simulation = game.simulate_game(init.COLOURS, init.GAME_LENGTH, init.GAME_WIDTH) | ||
for _ in game_simulation: | ||
game_round += 1 | ||
|
||
guess = possible_combinations[0] | ||
answer = game_simulation.send(guess) | ||
print(f"Guessed: {guess}; answer: {answer}") | ||
|
||
# Check if the game is won | ||
if answer[2]: | ||
print(f'Game won in {game_round + 1} guesses!') | ||
break | ||
|
||
possible_combinations = init.reduce(possible_combinations, guess, answer[:2]) | ||
else: | ||
print('Game lost.') | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Oops, something went wrong.