Skip to content

Instantly share code, notes, and snippets.

@JackDraak
Last active March 22, 2023 03:03
Show Gist options
  • Save JackDraak/2167aff9780b36b16dd83c2ddec9934d to your computer and use it in GitHub Desktop.
Save JackDraak/2167aff9780b36b16dd83c2ddec9934d to your computer and use it in GitHub Desktop.
Python Fifteen Game - console or pyGame GUI
from game_controller import GameController
class AIController(GameController):
def __init__(self):
super().__init__()
self.player_name = "Fred" # This can be changed to whatever name you prefer
self.is_ai = True
def get_move(self):
# Implement logic here to calculate the best move based on the current game state
# and return a tuple (row, col) with the coordinates of the move.
# For example, you could use your language model to generate a move based on the current board state.
# For now, let's assume that we always choose the first empty cell as our move:
for row in range(self.game_state.board_size):
for col in range(self.game_state.board_size):
if self.game_state.board[row][col] == "":
return (row, col)
# If no empty cell is found, return None
return None
from Game import Game
def command_check(command: str):
if command == "": # Default behaviour selected.
return command
else:
direction = (0, 0)
check_command = command.lower()[0]
if check_command == "q": # Quit.
quit_game()
elif check_command == "a": # Move blank left. (tile on left takes blank position)
direction = (0, -1)
elif check_command == "d": # Move blank right. (tile on right takes blank position)
direction = (0, 1)
elif check_command == "w": # Move blank up. (tile above takes blank position)
direction = (-1, 0)
elif check_command == "s": # Move blank down. (tile below takes blank position)
direction = (1, 0)
if direction != (0, 0):
return direction
return command
def input_game_size():
size_default = 4 # For the classic '15 puzzle', use a grid with a dimension of 4.
size_max = 31 # Grids with dimension >31 have >1000 tiles, would require re-formatting.
size_min = 3 # Grids with dimension <3 are not functionally playable.
size = size_default
print("\nGoal: slide the game tiles into the open position, 1-by-1, to re-order them. [Or (q)uit at any prompt]")
print("To play the classic tile game, '15', ", end="")
valid_input = False
while not valid_input:
grid_size = input(f"please choose a grid size from {size_min} to {size_max} [default: {size_default}] ")
grid_size = command_check(grid_size)
if grid_size == "": # Default value selected.
valid_input = True
elif type(grid_size) == tuple: # Reject WASD input; unrelated to game_size
pass
elif grid_size.isdigit():
size = int(grid_size)
if size_min <= size <= size_max:
valid_input = True
print()
return size
def input_shuffle(game: Game):
print("*** Congratulations, you solved the puzzle! ***\n")
print(game)
shuffled = False
while not shuffled:
shuffles = input(f"How many times would you like to shuffle? [default: {game.shuffle_default}] \n")
shuffles = command_check(shuffles)
if shuffles == "": # Default value selected.
game.shuffle(game.shuffle_default)
shuffled = True
pass
elif type(shuffles) == tuple: # Reject WASD input; unrelated to shuffling
pass
elif not shuffles.isdigit(): # Reject non-integer input; has no meaning
pass
else:
shuffled = game.shuffle(int(shuffles))
def input_turn(game: Game):
print(game)
player_move = input("Please, enter the label of the tile you would like to push into the gap.\n" +
f"Valid tiles to move: {game.get_valid_moves()} ")
player_move = command_check(player_move)
process_turn(game, player_move)
def play(game: Game):
while True:
input_turn(game)
if game.is_solved():
input_shuffle(game)
def process_turn(game: Game, player_move):
print()
if type(player_move) == tuple:
wasd_label = game.get_cardinal_label(player_move)
if game.get_valid_moves().__contains__(wasd_label):
game.slide_tile(int(wasd_label))
else:
print("Unable to move that direction...\n")
elif not player_move.isdigit():
print("Please, input a valid tile number to move...\n")
elif not game.slide_tile(int(player_move)):
print(f"Unable to move tile {player_move}...\n")
def quit_game():
print("\nThank you for playing 'fifteen'. Have a nice day! ")
quit()
if __name__ == '__main__':
play(Game(input_game_size(), True))
from Tile import Tile
import random
import usage
class Game:
def __init__(self, dimension: int, shuffled: bool):
entropy_factor = 100
self.dimension = dimension
self.blank_label = dimension * dimension
self.blank_position = dimension - 1, dimension - 1
self.shuffle_default = dimension * entropy_factor
self.solution = list(range(1, self.blank_label + 1))
self.tiles = self.generate_tiles(dimension)
if shuffled:
self.shuffle(self.shuffle_default)
def __repr__(self):
print_string = ""
for x in range(self.dimension):
print_string += "\t"
for y in range(self.dimension):
label = self.get_label(x, y)
if label != self.blank_label:
print_string += f"\t{label}"
else:
print_string += "\t"
print_string += "\n"
return print_string
# TODO fix or replace this function; it creates Game objects that lose state (extra Tile sans properties, post-move)
def duplicate(self):
duplicate_game = Game(self.dimension, False)
duplicate_game.import_tiles(self.export_tiles()) # Issues may lie here with export/import?
return duplicate_game
def export_tiles(self):
tiles = list()
for tile in self.tiles:
tiles.append(Tile(tile.label, tile.row, tile.column, tile.dimension))
return tiles
@staticmethod
def generate_tiles(dimension: int):
tiles = list()
label = 0
for row in range(dimension):
for column in range(dimension):
label += 1
tiles.append(Tile(label, row, column, dimension))
return tiles
def get_cardinal_label(self, direction: tuple):
delta = (direction[0] + self.blank_position[0]), (direction[1] + self.blank_position[1])
return self.get_label(delta[0], delta[1]) # Return tile.label based on position delta:blank
def get_distance_by_label(self, label: int):
for tile in self.tiles:
if tile.label == label:
return tile.distance()
return False
def get_distance_set(self):
label_pairs = list()
for row in range(self.dimension):
for column in range(self.dimension):
pair = list()
label = self.get_label(row, column)
this_pair = label, self.get_distance_by_label(label)
pair.append(this_pair)
label_pairs.append(pair)
return label_pairs
def get_distance_sum(self):
return sum(tile.distance() for tile in self.tiles)
def get_label(self, row: int, column: int):
for tile in self.tiles:
if tile.row == row and tile.column == column:
return tile.label
def get_labels_as_list(self): # Return tile-set labels as a 1D array.
tiles = list()
for row in range(self.dimension):
for column in range(self.dimension):
tiles.append(self.get_label(row, column))
return tiles
def get_labels_as_matrix(self): # Return tile-set labels as a 2D array.
tiles = list()
for row in range(self.dimension):
rows = list()
for column in range(self.dimension):
rows.append(self.get_label(row, column))
tiles.append(rows)
return tiles
def get_position(self, label: int):
for tile in self.tiles:
if tile.label == label:
return tile.row, tile.column
def get_valid_moves(self):
valid_moves = list()
blank_row, blank_column = self.blank_position
for tile in self.tiles:
if tile.row == blank_row: # Select horizontal neighbors.
if tile.column + 1 == blank_column or tile.column - 1 == blank_column:
valid_moves.append(tile.label)
if tile.column == blank_column: # Select vertical neighbors.
if tile.row + 1 == blank_row or tile.row - 1 == blank_row:
valid_moves.append(tile.label)
if valid_moves.__contains__(self.blank_label): # Trim blank-tile from set.
valid_moves.remove(self.blank_label)
return valid_moves
def import_tiles(self, tiles: list):
self.tiles = list()
self.tiles = tiles
def is_solved(self):
return self.solution == self.get_labels_as_list()
def print_tile_set(self):
for tile in self.tiles:
lab = tile.label
car = tile.cardinal
dim = tile.dimension
row = tile.row
col = tile.column
dis = self.get_distance_by_label(tile.label)
print(f"<Tile> label:{lab}({car}), position:({dim}){row},{col} distance:{dis}")
def set_tile_position(self, label: int, row: int, column: int):
for tile in self.tiles:
if tile.label == label:
tile.move_to(row, column)
return True
return False
def shuffle(self, moves: int):
last_move = int()
while moves > 0:
options = self.get_valid_moves()
if options.__contains__(last_move):
options.remove(last_move)
random_move = options[random.randint(0, len(options) - 1)]
self.slide_tile(random_move)
last_move = random_move
moves -= 1
return True
def slide_tile(self, label: int):
if self.get_valid_moves().__contains__(label):
this_blank_position = self.blank_position
this_tile_pos = self.get_position(label)
if not self.set_tile_position(label, this_blank_position[0], this_blank_position[1]): # Set pos of tile.
print(f"\n{self}Game.set_tile_position({label},{this_blank_position[0]},{this_blank_position[1]}) FAIL")
return False
if not self.set_tile_position(self.blank_label, this_tile_pos[0], this_tile_pos[1]): # Set pos of blank.
print(f"\n{self}Game.set_tile_position({self.blank_label},{this_tile_pos[0]},{this_tile_pos[1]}) FAIL")
return False
else:
self.blank_position = this_tile_pos[0], this_tile_pos[1]
return True
return False
if __name__ == '__main__':
usage.explain()
Here's some example code for a machine learning controller that trains a model based on the game data:
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
class MLController:
def __init__(self, data_file):
self.data_file = data_file
def train_model(self):
# Load data from file
data = pd.read_csv(self.data_file)
# Split data into features and labels
features = data.drop('label', axis=1)
labels = data['label']
# Train a machine learning model
model = RandomForestClassifier()
model.fit(features, labels)
return model
This code assumes that you have game data stored in a CSV file, where each row represents a game state and includes features
such as the positions of pieces on the board, the current player, etc. The train_model method reads in this CSV file,
preprocesses the data by splitting it into features and labels, and trains a RandomForestClassifier model on the data.
You can modify this code as needed to work with your game and desired machine learning algorithms.
from game import Game
import numpy as np
from sklearn.neural_network import MLPClassifier
from sklearn.externals import joblib
class ml_controller:
def init(self):
# Initialize empty arrays to store game data
self.data_X = []
self.data_y = []
# Play a few games to collect training data
self.play_games(50)
# Train the model on the collected data
self.train_model()
def play_games(self, n):
for i in range(n):
game = Game()
while not game.is_game_over():
# Replace the next line with your AI's decision-making algorithm
move = self.find_best_move(game)
game.make_move(move)
# Append the game data to the arrays
self.data_X.append(game.get_game_board())
self.data_y.append(game.get_winner())
def find_best_move(self, game):
# Replace this method with
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
class MLController:
def __init__(self, data_file):
self.data_file = data_file
def train_model(self):
# Load data from file
data = pd.read_csv(self.data_file)
# Split data into features and labels
features = data.drop('label', axis=1)
labels = data['label']
# Train a machine learning model
model = RandomForestClassifier()
model.fit(features, labels)
return model
import usage
class Tile:
def __init__(self, label: int, row: int, column: int, dimension: int):
self.cardinal = label
self.label = label
self.row = row
self.column = column
self.dimension = dimension
def __repr__(self):
lab = self.label
car = self.cardinal
dim = self.dimension
row = self.row
col = self.column
dis = self.distance()
return f"<Tile> label:{lab}({car}), position:({dim}){row},{col} distance:{dis}"
def distance(self):
lab = self.label
dim = self.dimension
row = self.row
col = self.column
row_dimension = row * dim
return abs(lab - col - row_dimension - 1)
def move_to(self, row: int, column: int):
self.row = row
self.column = column
def set(self, cardinal: int, label: int, row: int, column: int):
self.cardinal = cardinal
self.label = label
self.row = row
self.column = column
if __name__ == '__main__':
usage.explain()
@JackDraak
Copy link
Author

Asking ChatGPT4 to help me update this code to train a ML model.....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment