uhhhh
This commit is contained in:
parent
0d2340b7e8
commit
a74be2fc31
11 changed files with 2367 additions and 184 deletions
19
.direnv/bin/nix-direnv-reload
Executable file
19
.direnv/bin/nix-direnv-reload
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
if [[ ! -d "/home/mia/git/Nim-AI-template" ]]; then
|
||||||
|
echo "Cannot find source directory; Did you move it?"
|
||||||
|
echo "(Looking for "/home/mia/git/Nim-AI-template")"
|
||||||
|
echo 'Cannot force reload with this script - use "direnv reload" manually and then try again'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# rebuild the cache forcefully
|
||||||
|
_nix_direnv_force_reload=1 direnv exec "/home/mia/git/Nim-AI-template" true
|
||||||
|
|
||||||
|
# Update the mtime for .envrc.
|
||||||
|
# This will cause direnv to reload again - but without re-building.
|
||||||
|
touch "/home/mia/git/Nim-AI-template/.envrc"
|
||||||
|
|
||||||
|
# Also update the timestamp of whatever profile_rc we have.
|
||||||
|
# This makes sure that we know we are up to date.
|
||||||
|
touch -r "/home/mia/git/Nim-AI-template/.envrc" "/home/mia/git/Nim-AI-template/.direnv"/*.rc
|
||||||
1
.direnv/nix-profile-25.11-wl5m60vn27dl0dnq
Symbolic link
1
.direnv/nix-profile-25.11-wl5m60vn27dl0dnq
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/nix/store/swz054hgfjc292xpp0axf1p089mlcbcd-nix-shell-env
|
||||||
2124
.direnv/nix-profile-25.11-wl5m60vn27dl0dnq.rc
Normal file
2124
.direnv/nix-profile-25.11-wl5m60vn27dl0dnq.rc
Normal file
File diff suppressed because it is too large
Load diff
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use nix
|
||||||
BIN
__pycache__/game.cpython-313.pyc
Normal file
BIN
__pycache__/game.cpython-313.pyc
Normal file
Binary file not shown.
BIN
__pycache__/game.cpython-315.pyc
Normal file
BIN
__pycache__/game.cpython-315.pyc
Normal file
Binary file not shown.
BIN
__pycache__/nim.cpython-313.pyc
Normal file
BIN
__pycache__/nim.cpython-313.pyc
Normal file
Binary file not shown.
BIN
__pycache__/nim.cpython-315.pyc
Normal file
BIN
__pycache__/nim.cpython-315.pyc
Normal file
Binary file not shown.
363
game.py
363
game.py
|
|
@ -1,179 +1,184 @@
|
||||||
import pygame
|
import pygame
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
# Initialize Pygame
|
# Initialize Pygame
|
||||||
pygame.init()
|
pygame.init()
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
WIDTH, HEIGHT = 700, 500
|
WIDTH, HEIGHT = 700, 500
|
||||||
FPS = 30
|
FPS = 30
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLACK = (0, 0, 0)
|
BLACK = (0, 0, 0)
|
||||||
RED = (255, 0, 0)
|
RED = (255, 0, 0)
|
||||||
GREEN = (0, 255, 0)
|
GREEN = (0, 255, 0)
|
||||||
LIGHT_GREY = (200, 200, 200)
|
LIGHT_GREY = (200, 200, 200)
|
||||||
DARK_GREY = (50, 50, 50)
|
DARK_GREY = (50, 50, 50)
|
||||||
FONT = pygame.font.Font(None, 36)
|
FONT = pygame.font.Font(None, 36)
|
||||||
|
|
||||||
# Create screen
|
# Create screen
|
||||||
screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
||||||
pygame.display.set_caption('Nim Game')
|
pygame.display.set_caption('Nim Game')
|
||||||
|
|
||||||
# Define piles (number of coins in each pile)
|
# Define piles (number of coins in each pile)
|
||||||
piles = [4, 4, 4, 4] # 4 piles with 4 coins each
|
piles = [4, 4, 4, 4] # 4 piles with 4 coins each
|
||||||
selected_stones = [] # To store selected stones for removal
|
selected_stones = [] # To store selected stones for removal
|
||||||
selected_pile = None # Tracks the pile from which coins are selected
|
selected_pile = None # Tracks the pile from which coins are selected
|
||||||
|
|
||||||
# Players
|
# Players
|
||||||
player_turn = 1 # Player 1 starts (alternates between 1 and 2)
|
player_turn = 1 # Player 1 starts (alternates between 1 and 2)
|
||||||
|
|
||||||
# Game State
|
# Game State
|
||||||
game_over = False
|
game_over = False
|
||||||
winner = None
|
winner = None
|
||||||
|
|
||||||
def draw_piles():
|
def draw_piles():
|
||||||
"""Draws the piles of coins as circles with padding."""
|
"""Draws the piles of coins as circles with padding."""
|
||||||
x_pos = 130 # Start from left with padding
|
x_pos = 130 # Start from left with padding
|
||||||
y_start = 250
|
y_start = 250
|
||||||
padding = 150 # Increased space between piles for better symmetry
|
padding = 150 # Increased space between piles for better symmetry
|
||||||
radius = 20
|
radius = 20
|
||||||
for idx, pile in enumerate(piles):
|
for idx, pile in enumerate(piles):
|
||||||
y_pos = y_start
|
y_pos = y_start
|
||||||
for stone in range(pile):
|
for stone in range(pile):
|
||||||
color = RED
|
color = RED
|
||||||
if (idx, stone) in selected_stones:
|
if (idx, stone) in selected_stones:
|
||||||
color = GREEN # Show selected stones as green
|
color = GREEN # Show selected stones as green
|
||||||
pygame.draw.circle(screen, color, (x_pos, y_pos), radius)
|
pygame.draw.circle(screen, color, (x_pos, y_pos), radius)
|
||||||
y_pos -= 2 * radius + 10 # Space between circles
|
y_pos -= 2 * radius + 10 # Space between circles
|
||||||
text = FONT.render(f'Pile {idx + 1}', True, BLACK)
|
text = FONT.render(f'Pile {idx + 1}', True, BLACK)
|
||||||
screen.blit(text, (x_pos - 30, 320))
|
screen.blit(text, (x_pos - 30, 320))
|
||||||
x_pos += padding # Increase x position and add padding
|
x_pos += padding # Increase x position and add padding
|
||||||
|
|
||||||
def check_game_over():
|
def check_game_over():
|
||||||
"""Check if the game is over (all piles empty)."""
|
"""Check if the game is over (all piles empty)."""
|
||||||
global winner, game_over
|
global winner, game_over
|
||||||
if all(pile == 0 for pile in piles):
|
if all(pile == 0 for pile in piles):
|
||||||
winner = 2 if player_turn == 1 else 1 # The other player wins
|
winner = 2 if player_turn == 1 else 1 # The other player wins
|
||||||
game_over = True
|
game_over = True
|
||||||
|
|
||||||
def draw_game_state():
|
def draw_game_state():
|
||||||
"""Draws the current game state including piles and turn."""
|
"""Draws the current game state including piles and turn."""
|
||||||
screen.fill(LIGHT_GREY) # Background color
|
screen.fill(LIGHT_GREY) # Background color
|
||||||
draw_piles()
|
draw_piles()
|
||||||
|
|
||||||
if game_over:
|
if game_over:
|
||||||
if player_turn == 1:
|
if player_turn == 1:
|
||||||
text = FONT.render(f'You win, hooray!', True, GREEN)
|
text = FONT.render(f'You win, hooray!', True, GREEN)
|
||||||
screen.blit(text, (WIDTH // 2 - text.get_width() // 2, 30)) # Adjusted y-position for spacing
|
screen.blit(text, (WIDTH // 2 - text.get_width() // 2, 30)) # Adjusted y-position for spacing
|
||||||
else:
|
else:
|
||||||
text = FONT.render(f'AI wins!', True, GREEN)
|
text = FONT.render(f'AI wins!', True, GREEN)
|
||||||
screen.blit(text, (WIDTH // 2 - text.get_width() // 2, 30)) # Adjusted y-position for spacing
|
screen.blit(text, (WIDTH // 2 - text.get_width() // 2, 30)) # Adjusted y-position for spacing
|
||||||
|
|
||||||
# Draw the Restart button
|
# Draw the Restart button
|
||||||
pygame.draw.rect(screen, DARK_GREY, (WIDTH // 2 - 60, HEIGHT - 60, 120, 40))
|
pygame.draw.rect(screen, DARK_GREY, (WIDTH // 2 - 60, HEIGHT - 60, 120, 40))
|
||||||
restart_text = FONT.render("Restart", True, WHITE)
|
restart_text = FONT.render("Restart", True, WHITE)
|
||||||
screen.blit(restart_text, (WIDTH // 2 - restart_text.get_width() // 2, HEIGHT - 50))
|
screen.blit(restart_text, (WIDTH // 2 - restart_text.get_width() // 2, HEIGHT - 50))
|
||||||
else:
|
else:
|
||||||
if player_turn == 1:
|
if player_turn == 1:
|
||||||
text = FONT.render(f'Your turn!', True, BLACK)
|
text = FONT.render(f'Your turn!', True, BLACK)
|
||||||
screen.blit(text, (WIDTH // 2 - text.get_width() // 2, 30)) # Centered player label
|
screen.blit(text, (WIDTH // 2 - text.get_width() // 2, 30)) # Centered player label
|
||||||
else:
|
else:
|
||||||
text = FONT.render(f'Computer thinking... ', True, BLACK)
|
text = FONT.render(f'Computer thinking... ', True, BLACK)
|
||||||
screen.blit(text, (WIDTH // 2 - text.get_width() // 2, 30)) # Centered player labe
|
screen.blit(text, (WIDTH // 2 - text.get_width() // 2, 30)) # Centered player labe
|
||||||
|
|
||||||
|
|
||||||
# Draw the "Remove" button at the bottom
|
# Draw the "Remove" button at the bottom
|
||||||
pygame.draw.rect(screen, BLACK, (WIDTH // 2 - 60, HEIGHT - 120, 120, 40))
|
pygame.draw.rect(screen, BLACK, (WIDTH // 2 - 60, HEIGHT - 120, 120, 40))
|
||||||
remove_text = FONT.render("Remove", True, WHITE)
|
remove_text = FONT.render("Remove", True, WHITE)
|
||||||
screen.blit(remove_text, (WIDTH // 2 - remove_text.get_width() // 2, HEIGHT - 110))
|
screen.blit(remove_text, (WIDTH // 2 - remove_text.get_width() // 2, HEIGHT - 110))
|
||||||
|
|
||||||
def remove_stones():
|
def remove_stones():
|
||||||
"""Removes the selected stones from the selected pile."""
|
"""Removes the selected stones from the selected pile."""
|
||||||
global player_turn, selected_pile
|
global player_turn, selected_pile
|
||||||
for pile_index, stone_index in selected_stones:
|
if not selected_stones:
|
||||||
piles[pile_index] -= 1
|
return # Do nothing if nothing is selected
|
||||||
selected_stones.clear()
|
for pile_index, stone_index in selected_stones:
|
||||||
selected_pile = None # Reset selected pile after removal
|
piles[pile_index] -= 1
|
||||||
player_turn = 2 if player_turn == 1 else 1 # Switch turns
|
selected_stones.clear()
|
||||||
check_game_over()
|
selected_pile = None # Reset selected pile after removal
|
||||||
|
player_turn = 2 if player_turn == 1 else 1 # Switch turns
|
||||||
def handle_selection(pile_index, stone_index):
|
check_game_over()
|
||||||
"""Handles selecting or deselecting stones, ensuring only one pile can be selected."""
|
|
||||||
global selected_pile
|
def handle_selection(pile_index, stone_index):
|
||||||
if selected_pile is None or selected_pile == pile_index:
|
"""Handles selecting or deselecting stones, ensuring only one pile can be selected."""
|
||||||
selected_pile = pile_index # Lock the selection to the current pile
|
global selected_pile
|
||||||
if (pile_index, stone_index) in selected_stones:
|
if selected_pile is None or selected_pile == pile_index:
|
||||||
selected_stones.remove((pile_index, stone_index)) # Deselect
|
if (pile_index, stone_index) in selected_stones:
|
||||||
else:
|
selected_stones.remove((pile_index, stone_index)) # Deselect
|
||||||
selected_stones.append((pile_index, stone_index)) # Select
|
# If no stones are selected anymore, allow switching piles
|
||||||
|
if not selected_stones:
|
||||||
def restart_game():
|
selected_pile = None
|
||||||
"""Restarts the game."""
|
else:
|
||||||
global piles, player_turn, selected_stones, selected_pile, game_over, winner
|
selected_stones.append((pile_index, stone_index)) # Select
|
||||||
piles = [4, 4, 4, 4] # Reset piles
|
selected_pile = pile_index # Lock the selection to the current pile
|
||||||
selected_stones.clear()
|
|
||||||
selected_pile = None
|
def restart_game():
|
||||||
player_turn = 1
|
"""Restarts the game."""
|
||||||
game_over = False
|
global piles, player_turn, selected_stones, selected_pile, game_over, winner
|
||||||
winner = None
|
piles = [4, 4, 4, 4] # Reset piles
|
||||||
|
selected_stones.clear()
|
||||||
def start_game(ai):
|
selected_pile = None
|
||||||
"""Starts the game and integrates AI for playing against the computer."""
|
player_turn = 1
|
||||||
global player_turn, game_over
|
game_over = False
|
||||||
|
winner = None
|
||||||
# Main game loop
|
|
||||||
clock = pygame.time.Clock()
|
def start_game(ai):
|
||||||
while True:
|
"""Starts the game and integrates AI for playing against the computer."""
|
||||||
clock.tick(FPS)
|
global player_turn, game_over
|
||||||
|
|
||||||
for event in pygame.event.get():
|
# Main game loop
|
||||||
if event.type == pygame.QUIT:
|
clock = pygame.time.Clock()
|
||||||
pygame.quit()
|
while True:
|
||||||
sys.exit()
|
clock.tick(FPS)
|
||||||
elif event.type == pygame.MOUSEBUTTONDOWN and not game_over and player_turn == 1:
|
|
||||||
mouse_x, mouse_y = event.pos
|
for event in pygame.event.get():
|
||||||
# Check for pile selection (clicking on a coin)
|
if event.type == pygame.QUIT:
|
||||||
x_pos = 130
|
pygame.quit()
|
||||||
y_start = 250
|
sys.exit()
|
||||||
padding = 150
|
elif event.type == pygame.MOUSEBUTTONDOWN and not game_over and player_turn == 1:
|
||||||
radius = 20
|
mouse_x, mouse_y = event.pos
|
||||||
for pile_index, pile in enumerate(piles):
|
# Check for pile selection (clicking on a coin)
|
||||||
y_pos = y_start
|
x_pos = 130
|
||||||
for stone_index in range(pile):
|
y_start = 250
|
||||||
dist = ((mouse_x - x_pos)**2 + (mouse_y - y_pos)**2)**0.5
|
padding = 150
|
||||||
if dist <= radius:
|
radius = 20
|
||||||
handle_selection(pile_index, stone_index)
|
for pile_index, pile in enumerate(piles):
|
||||||
y_pos -= 2 * radius + 10
|
y_pos = y_start
|
||||||
x_pos += padding
|
for stone_index in range(pile):
|
||||||
# Check for "Remove" button click
|
dist = ((mouse_x - x_pos)**2 + (mouse_y - y_pos)**2)**0.5
|
||||||
if WIDTH // 2 - 60 <= mouse_x <= WIDTH // 2 + 60 and HEIGHT - 120 <= mouse_y <= HEIGHT - 80:
|
if dist <= radius:
|
||||||
remove_stones()
|
handle_selection(pile_index, stone_index)
|
||||||
|
y_pos -= 2 * radius + 10
|
||||||
# If game over, check for "Restart" button click
|
x_pos += padding
|
||||||
elif event.type == pygame.MOUSEBUTTONDOWN and game_over:
|
# Check for "Remove" button click
|
||||||
mouse_x, mouse_y = event.pos
|
if WIDTH // 2 - 60 <= mouse_x <= WIDTH // 2 + 60 and HEIGHT - 120 <= mouse_y <= HEIGHT - 80:
|
||||||
if WIDTH // 2 - 60 <= mouse_x <= WIDTH // 2 + 60 and HEIGHT - 60 <= mouse_y <= HEIGHT - 20:
|
remove_stones()
|
||||||
restart_game()
|
|
||||||
|
# If game over, check for "Restart" button click
|
||||||
# If it's the AI's turn and the game is not over
|
elif event.type == pygame.MOUSEBUTTONDOWN and game_over:
|
||||||
if player_turn == 2 and not game_over:
|
mouse_x, mouse_y = event.pos
|
||||||
draw_game_state()
|
if WIDTH // 2 - 60 <= mouse_x <= WIDTH // 2 + 60 and HEIGHT - 60 <= mouse_y <= HEIGHT - 20:
|
||||||
pygame.display.flip()
|
restart_game()
|
||||||
# Simulate thinking
|
|
||||||
time.sleep(2)
|
# If it's the AI's turn and the game is not over
|
||||||
# AI makes its move
|
if player_turn == 2 and not game_over:
|
||||||
action = ai.choose_action(piles, epsilon=False)
|
draw_game_state()
|
||||||
remove_stones_from_ai(action)
|
pygame.display.flip()
|
||||||
|
# Simulate thinking
|
||||||
draw_game_state()
|
time.sleep(2)
|
||||||
pygame.display.flip()
|
# AI makes its move
|
||||||
|
action = ai.choose_action(piles, epsilon=False)
|
||||||
def remove_stones_from_ai(action):
|
remove_stones_from_ai(action)
|
||||||
"""Handles AI stone removal."""
|
|
||||||
pile, count = action
|
draw_game_state()
|
||||||
for i in range(count):
|
pygame.display.flip()
|
||||||
piles[pile] -= 1
|
|
||||||
global player_turn
|
def remove_stones_from_ai(action):
|
||||||
player_turn = 1 # Switch back to human player
|
"""Handles AI stone removal."""
|
||||||
check_game_over()
|
pile, count = action
|
||||||
|
for i in range(count):
|
||||||
|
piles[pile] -= 1
|
||||||
|
global player_turn
|
||||||
|
player_turn = 1 # Switch back to human player
|
||||||
|
check_game_over()
|
||||||
|
|
|
||||||
33
nim.py
33
nim.py
|
|
@ -56,11 +56,17 @@ class NimAI():
|
||||||
float: The Q-value associated with the (state, action) pair.
|
float: The Q-value associated with the (state, action) pair.
|
||||||
Returns 0 if the pair is not yet in the Q-table.
|
Returns 0 if the pair is not yet in the Q-table.
|
||||||
"""
|
"""
|
||||||
print(self.q)
|
print(self.q, state, action)
|
||||||
|
try:
|
||||||
|
return self.q[(tuple(state), action)]
|
||||||
|
except:
|
||||||
|
return 0
|
||||||
|
|
||||||
def update_q_value(self, state, action, old_q, reward, future_q):
|
def update_q_value(self, state, action, old_q, reward, future_q):
|
||||||
"""
|
"""
|
||||||
Update the Q-value for a state-action pair using the Q-learning formula.
|
Update the Q-value for a state-action pair using the Q-learning formula.
|
||||||
|
|
||||||
|
Q(s, a) ← Q(s, a) + α * (Belohnung + γ * max_a' Q(s', a') - Q(s, a))
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
state (list): The current game state.
|
state (list): The current game state.
|
||||||
|
|
@ -69,10 +75,11 @@ class NimAI():
|
||||||
reward (float): The reward received after taking the action.
|
reward (float): The reward received after taking the action.
|
||||||
future_q (float): The maximum Q-value for the next state.
|
future_q (float): The maximum Q-value for the next state.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
self.q[tuple(state), action] = old_q + self.alpha * (reward + self.epsilon * future_q - old_q)
|
||||||
|
return 0
|
||||||
|
|
||||||
def best_future_reward(self, state):
|
def best_future_reward(self, state):
|
||||||
"""
|
"""
|
||||||
Determine the highest Q-value among all possible actions in a given state.
|
Determine the highest Q-value among all possible actions in a given state.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
@ -82,7 +89,15 @@ class NimAI():
|
||||||
float: The highest Q-value among available actions.
|
float: The highest Q-value among available actions.
|
||||||
Returns 0 if no actions are available.
|
Returns 0 if no actions are available.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
# actions = []
|
||||||
|
# for q in self.q.key:
|
||||||
|
# if q[0] == state:
|
||||||
|
# actions.append(q[1])
|
||||||
|
actions = tuple([key[1] for key in self.q.keys() if key[0] == state])
|
||||||
|
try:
|
||||||
|
return max([q for q in self.q[tuple(state), actions]])
|
||||||
|
except:
|
||||||
|
return 0
|
||||||
|
|
||||||
def choose_action(self, state, epsilon=True):
|
def choose_action(self, state, epsilon=True):
|
||||||
"""
|
"""
|
||||||
|
|
@ -95,7 +110,15 @@ class NimAI():
|
||||||
Returns:
|
Returns:
|
||||||
tuple: The chosen action from the available actions.
|
tuple: The chosen action from the available actions.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
if epsilon:
|
||||||
|
return random.choice(tuple(Nim.available_actions(state)))
|
||||||
|
# keys = [key[1] for key in self.q.key if key[0] == state]
|
||||||
|
# for key in keys:
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return max([key[1] for key in self.q.keys() if key[0] == state])
|
||||||
|
except:
|
||||||
|
return (0,0)
|
||||||
|
|
||||||
def train(n):
|
def train(n):
|
||||||
player = NimAI()
|
player = NimAI()
|
||||||
|
|
|
||||||
10
shell.nix
Normal file
10
shell.nix
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
let
|
||||||
|
pkgs = import <nixpkgs> {};
|
||||||
|
in pkgs.mkShell {
|
||||||
|
packages = [
|
||||||
|
(pkgs.python3.withPackages (python-pkgs: [
|
||||||
|
python-pkgs.pygame
|
||||||
|
]))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue