diff --git a/Baloo2-Bold.ttf b/Baloo2-Bold.ttf new file mode 100644 index 0000000..c2f6f69 Binary files /dev/null and b/Baloo2-Bold.ttf differ diff --git a/README.md b/README.md index c798e08..2402b49 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,6 @@ pvp lan todo: remember the last ip you played against. remember ip input with a name? stop multiple ppl joining one game (or add spectators) make it so when ppl leave, the game ends instead of hanging or crashing. + +player vs computer todo: + sometimes it prioritizes vertical 3-in-a-rows instead of a win. (balance threat and winning) \ No newline at end of file diff --git a/button.py b/button.py new file mode 100644 index 0000000..cfa9884 --- /dev/null +++ b/button.py @@ -0,0 +1,30 @@ +import pygame + +class Button: + def __init__(self, x, y, width, height, text, color, hover_color, text_color, action, font, font_size, extra_data = None): + self.rect = pygame.Rect(x, y, width, height) + self.text = text + self.color = color + self.hover_color = hover_color + self.text_color = text_color + self.current_color = color + self.action = action + self.font = pygame.font.Font(font, font_size) + self.extra_data = extra_data + + def draw(self, screen): + pygame.draw.rect(screen, self.current_color, self.rect) + text_surface = self.font.render(self.text, True, self.text_color) + text_rect = text_surface.get_rect(center=self.rect.center) + screen.blit(text_surface, text_rect) + + def handle_event(self, event): + if event.type == pygame.MOUSEMOTION: + if self.rect.collidepoint(event.pos): + self.current_color = self.hover_color + else: + self.current_color = self.color + + elif event.type == pygame.MOUSEBUTTONDOWN: + if self.rect.collidepoint(event.pos): + self.action(self) diff --git a/guitest.py b/guitest.py new file mode 100644 index 0000000..d5ca7c6 --- /dev/null +++ b/guitest.py @@ -0,0 +1,111 @@ +# Imports +import pygame +from pygame import Vector2 as v2, Color as Colour + +from button import Button + +# some constst +WINDOW_SIZE = (768, 768) +WINDOW_SCALE = 1 +TARGET_FPS = 60 + +# pygame inits +pygame.init() +display = pygame.display.set_mode(v2(WINDOW_SIZE)*WINDOW_SCALE) +clock = pygame.time.Clock() + +# more variable inits +menu = "start" +tiles = [] + +# menu functions +def change_menu(targetMenu): + global menu + menu = targetMenu + display.fill('black') + +def start_game_func(*_): + change_menu("game") + +def settings_menu(*_): + change_menu("settings") + +def go_back(*_): + change_menu("start") + +# gets called when you click on a tile +def tile_press(tile): + tile_id,x,y = tile.extra_data + print(f"TILE {tile_id} at {x},{y} PRESSED") + +# Main block +if __name__ == "__main__": + # You're running the game, therefore running = True + running = True + + # Button inits + width = 280 + height = 75 + + x = WINDOW_SIZE[0] / 2 - width / 2 # center of the screen horizontally + y = (WINDOW_SIZE[1] / 2 - height / 2) # center of the screen vertically + + start_button = Button(x, y - 100, width, height, "Start Game", (0, 150, 0), (255, 0, 0), (255, 255, 255), start_game_func, "Baloo2-Bold.ttf", 50) + settings_button = Button(x, y-100+height*2, width, height, "Settings", (0, 150, 0), (255, 0, 0), (255, 255, 255), settings_menu, "Baloo2-Bold.ttf", 50) + go_back_button = Button(x, y, width, height, "Go back", (0, 150, 0), (255, 0, 0), (255, 255, 255), go_back, "Baloo2-Bold.ttf", 50) + + # Game loop + while running: + + # handles user input + for event in pygame.event.get(): + # Lets you actually close the game, or ESC out + if event.type == pygame.QUIT: + running = False + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + running = False + + # Handles all the inputs for the buttons + if menu == "start": + start_button.handle_event(event) + settings_button.handle_event(event) + elif menu == "settings": + go_back_button.handle_event(event) + elif menu == "game": + for tile in tiles: + tile.handle_event(event) + else: + # very descriptive error message + print("You broke smth idek what tbh") + running = False + + # Display stuff! + # so depending on what menu you're in, draw different stuff + if menu == "start": + start_button.draw(display) + settings_button.draw(display) + elif menu == "settings": + go_back_button.draw(display) + # basic connect-4 ahh grid + elif menu == "game": + COLS = 7 + ROWS = 6 + + tiles = [] + + for c in range(COLS): + for r in range(ROWS): + tile = Button(50*c+50, 50*r+50, 30, 30, str(len(tiles)), (255, 255, 255), (150, 150, 150), (255, 0, 0), tile_press, None, 30, (len(tiles),c,r)) + tiles.append(tile) + + for tile in tiles: + tile.draw(display) + else: + # very descriptive error msg + print("you broke smth.") + running = False + + # flip the display and clock the tick so stuff actually updates + pygame.display.flip() + clock.tick(TARGET_FPS) diff --git a/main.py b/main.py index 78f0c56..3452a6d 100644 --- a/main.py +++ b/main.py @@ -83,10 +83,6 @@ def printBoard(board): ============================={C.END}""" bottom = f"{C.BOLD}==1===2===3===4===5===6===7=={C.END}" -# print(f""" {C.BOLD}CONNECT FOUR -# ============================={C.END} -# {'\n'.join(rows)} -# {C.BOLD}==1===2===3===4===5===6===7=={C.END}""") print(f"{top}\n{'\n'.join(rows)}\n{bottom}") @@ -124,13 +120,18 @@ def checkWin(board, player): if all(board[col + i][row - i] == player for i in range(winCount)): return [(col + i, row - i) for i in range(winCount)] +def checkFull(board): + if all('O' not in col for col in board): + return True + + return False + def isTerminalNode(board): if checkWin(board, 'R'): return "WinX" elif checkWin(board, 'Y'): return "WinY" - - if all('O' not in col for col in board): + elif checkFull(board): return "Draw" return False @@ -253,7 +254,17 @@ def cpu_move_provider(player, board): best_score = float('-inf') if player == 'R' else float('inf') best_move = None + # try: + # with open("settings.json", "r") as f: + # settings = json.load(f) + # print(f"Settings: {settings}") + # search_depth = settings.get("cpu_search_depth", 5) + # print(f"search depth: {search_depth}") + # except (FileNotFoundError, json.JSONDecodeError): + # search_depth = 5 + search_depth = 5 + maximising = True if player == 'R' else False for move in allowedMoves: @@ -288,7 +299,6 @@ def play_game(player1_get_move, player2_get_move): clear() printBoard(board) - # Get column from correct player if player == 'R': col = player1_get_move(player, board) else: @@ -310,6 +320,13 @@ def play_game(player1_get_move, player2_get_move): print(f"{colourTile(player)} won!") input("Press ENTER to return to the menu.") break + + if checkFull(board): + clear() + printBoard(board) + print("Its a draw!") + input("Press ENTER to return to the menu.") + break player = 'Y' if player == 'R' else 'R' @@ -321,12 +338,12 @@ def play_local_pvp(): def play_lan_server(): print("PvP LAN is in maintenance due to exploits.!") - input("Press Enter to return to menu...") + input("Press ENTER to return to menu...") return def play_lan_client(): print("PvP LAN is in maintenance due to exploits.!") - input("Press Enter to return to menu...") + input("Press ENTER to return to menu...") return def play_vs_computer(): @@ -338,7 +355,7 @@ def play_vs_computer(): raise ValueError break except ValueError: - print("Enter 'r', 'red', 'y' or 'yellow'.") + print("ENTER 'r', 'red', 'y' or 'yellow'.") if inp in ["r", "red"]: play_game(local_move_provider, cpu_move_provider) @@ -357,7 +374,8 @@ def edit_settings(): # Default settings if no file exists default_settings = { - "display_mode": "coloured_text" # options: coloured_text, coloured_background, emojis + "display_mode": "coloured_text", # options: coloured_text, coloured_background, emojis + "cpu_search_depth": 5 # options: 1-9 } # Load existing settings @@ -377,12 +395,13 @@ def edit_settings(): with open(settings_file, "w") as f: json.dump(settings, f, indent=4) print("Settings saved.") - input("Press Enter to return to main menu...") + input("Press ENTER to return to main menu...") while True: clear() print("=== Settings Menu ===") print("1. Display Mode") + print("2. CPU Search Depth") print("--------------------") print("S. Save and Exit") print("E. Exit without Saving") @@ -414,7 +433,31 @@ def edit_settings(): settings["display_mode"] = modes[int(sub_choice) - 1][0] print(f"Display mode set to {settings['display_mode']}") else: - input("Invalid choice. Press Enter to try again...") + input("Invalid choice. Press ENTER to try again...") + + elif choice == "2": + # CPU Search Depth submenu + while True: + clear() + print("=== CPU Search Depth ===") + + print(f"Depth: {C.BOLD}{settings["cpu_search_depth"]}{C.END}") + print("B. Go Back") + + sub_choice = input("Choose a value 1-9, or the use + / - keys: ").strip() + if sub_choice.lower() == 'b': + break + elif sub_choice in ["1","2","3","4","5","6","7","8","9"]: + settings["cpu_search_depth"] = sub_choice + elif sub_choice in ['+', '-']: + settings["cpu_search_depth"] = eval(f"{settings["cpu_search_depth"]} {sub_choice}1") + if settings["cpu_search_depth"] > 9: + settings["cpu_search_depth"] = 9 + elif settings["cpu_search_depth"] < 1: + settings["cpu_search_depth"] = 1 + else: + input("Invalid choice. Press ENTER to try again...") + elif choice == "save" or choice == "s": save_settings() @@ -429,7 +472,7 @@ def edit_settings(): return else: - input("Invalid choice. Press Enter to try again...") + input("Invalid choice. Press ENTER to try again...") while True: clear() @@ -457,4 +500,29 @@ while True: elif choice == "6": break else: - input("Invalid choice. Press Enter to try again...") + input("Invalid choice. Press ENTER to try again...") + +""" +RED Drawstring +3 +4 +4 +3 +5 +3 +3 +3 +5 +1 +1 +1 +7 +7 +7 +2 +2 +2 +6 +6 +6 +""" \ No newline at end of file diff --git a/settings.json b/settings.json index 56538db..b4b8139 100644 --- a/settings.json +++ b/settings.json @@ -1,3 +1,4 @@ { - "display_mode": "coloured_background" + "display_mode": "emojis", + "cpu_search_depth": 6 } \ No newline at end of file