Skip to content

Commit

Permalink
Implemented AI
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrius Chamentauskas committed May 26, 2010
1 parent 195a3b0 commit 0744403
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 28 deletions.
25 changes: 15 additions & 10 deletions puyo/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def __init__(self, x, size, puyo_size):
pygame.sprite.Group.__init__(self)
self.rows_count = size[0]
self.cols_count = size[1]
Movable.__init__(self, pygame.Rect(x, 10 + puyo_size[1], puyo_size[0] * self.cols_count, puyo_size[1] * (self.rows_count - 1)))
Movable.__init__(self, pygame.Rect(x, 10 + puyo_size[1], puyo_size[0] * self.cols_count, puyo_size[1] * self.rows_count))

self.background = pygame.Surface(self.rect.size)
self.background.fill((240, 240, 240))
Expand All @@ -24,6 +24,7 @@ def spawn_puyo_pair(self):
self.score.generate_next_pair()
self._board = None
self.score.chain = 0
self.spawned = True

def move(self, right):
if not self.current_pair:
Expand Down Expand Up @@ -61,7 +62,7 @@ def rotate(self):

def __spawn_puyo(self, index):
puyo = Puyo(self, self.score.next_pair[index].color, index - 1)
if not (self.board[puyo.row][puyo.col] is None):
if puyo.row >= 0 and not (self.board[puyo.row][puyo.col] is None):
self.game_over = True
self.add(puyo)
return puyo
Expand All @@ -79,12 +80,13 @@ def __spawn_neutrals(self):
self.score.drop_neutrals = 0

def update(self, fast_forward = False):
self.spawned = False
pygame.sprite.Group.update(self)
self.__reset_current_pair()
self.__reset_current_pair(fast_forward)

dropping = False
for puyo in sorted(self, key=lambda puyo: puyo.row, reverse=True):
speed = 15 if fast_forward and puyo in self.current_pair else puyo.speed
speed = puyo.speed(fast_forward and puyo in self.current_pair)
if self.__move_puyo(puyo, 0, speed):
dropping = True

Expand All @@ -97,9 +99,9 @@ def update(self, fast_forward = False):
self.spawn_puyo_pair()
self._board = None

def __reset_current_pair(self):
def __reset_current_pair(self, fast_forward):
for puyo in self.current_pair:
if not self.__can_move(puyo, 0, 1, self.current_pair):
if not self.__can_move(puyo, 0, puyo.speed(fast_forward), self.current_pair):
self.current_pair = ()
self.state = 'scoring'
return
Expand Down Expand Up @@ -156,15 +158,18 @@ def __can_move(self, puyo, dx, dy, non_blocking_puyos = ()):
elif y >= self.height or x < 0 or x >= self.width:
return False
else:
blocking_puyo = self.board[row][col]
return blocking_puyo is None or blocking_puyo.row != row or blocking_puyo.col != col or (blocking_puyo in non_blocking_puyos)
blocking_puyo = None
for puyo in self:
if puyo.row == row and puyo.col == col:
blocking_puyo = puyo
return blocking_puyo is None or (blocking_puyo in non_blocking_puyos)

def __empty_board(self):
def empty_board(self):
return [[None for col in range(self.cols_count)] for row in range(self.rows_count)]

def get_board(self):
if self._board is None:
self._board = self.__empty_board()
self._board = self.empty_board()
for puyo in self:
if puyo.row >= 0:
self._board[puyo.row][puyo.col] = puyo
Expand Down
21 changes: 13 additions & 8 deletions puyo/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
pygame.display.flip()

puyo_size = (48, 48)
board_size = (12, 6)
board_size = (11, 6)
player1 = HumanPlayer(Board(10, board_size, puyo_size), (K_UP, K_LEFT, K_RIGHT, K_DOWN))
player2 = ComputerPlayer(Board(410, board_size, puyo_size))
clock = pygame.time.Clock()

font = pygame.font.SysFont(None, 150)
pause = False

while True:
clock.tick(60)
Expand All @@ -36,21 +37,25 @@
if event.type == QUIT:
sys.exit(0)
elif event.type == KEYDOWN:
if event.key == K_p:
pause = not pause
player1.key_press(event.key)
player2.key_press(event.key)

player1.update()
player2.update()
player1.drop_neutrals(player2)
player2.drop_neutrals(player1)

screen.blit(background, (0, 0))

if player1.game_over() or player2.game_over():
text = "Player 2 won!" if player1.game_over() else "Player 1 won!"
screen.blit(background, (0, 0))
screen.blit(font.render(text, True, (0, 255, 0)), (50, 250))
else:
if not pause:
player1.update()
player2.update()
player1.drop_neutrals(player2)
player2.drop_neutrals(player1)

screen.blit(background, (0, 0))
player1.board.draw(screen)
player2.board.draw(screen)

pygame.display.flip()

152 changes: 151 additions & 1 deletion puyo/player.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pygame
import sys

class Player(object):
def __init__(self, board):
Expand Down Expand Up @@ -32,9 +33,158 @@ def update(self):
Player.update(self, pygame.key.get_pressed()[self.speed_key])

class ComputerPlayer(Player):
def __init__(self, board):
Player.__init__(self, board)
self.next_move()

def key_press(self, key):
pass

def update(self):
Player.update(self, False)
fast = True
if self.move[1] > 0:
self.board.rotate()
self.move[1] -= 1
fast = False
elif self.board.current_pair:
if self.move[0] > self.board.current_pair[0].col:
self.board.move(True)
fast = False
elif self.move[0] < self.board.current_pair[0].col:
self.board.move(False)
fast = False
else:
fast = False

Player.update(self, fast)
if self.board.spawned:
self.next_move()

def next_move(self):
board = self._clone_board()
start_score = self._calculate_points(board)
pair = [puyo.color for puyo in self.board.current_pair]
next_pair = [puyo.color for puyo in self.board.score.next_pair]

max_score = -10000000000000

for p, cols, rot in self._get_drops(pair):
new_board = self._drop_pair(board, p, cols)
score = self._delete_touching(new_board) - 30
for next_p, next_cols, next_rot in self._get_drops(next_pair):
next_board = self._drop_pair(new_board, next_p, next_cols)
next_score = self._calculate_points(next_board) - pow(self._max_height(next_board), 2) - start_score
if next_score > score:
score = next_score
if score > max_score:
best_move = [cols[0], rot, score]
max_score = score

self.move = best_move


def _get_drops(self, pair):
drops = []
for col in range(self.board.cols_count):
drops.append((pair, (col, col), 0))
if col > 0:
drops.append((pair, (col, col - 1), 1))
drops.append(((pair[1], pair[0]), (col, col), 2))
if col < self.board.cols_count - 1:
drops.append((pair, (col, col + 1), 3))
return drops

def _delete_touching(self, board):
points = 0
visited = [[False for puyo in row] for row in board]
deleted = True
while deleted:
deleted = False
for row in range(len(board)):
for col in range(len(board[row])):
p = self._calculate_points_from(board, visited, row, col, board[row][col])
if p >= 4:
deleted = True
points += p * p
self._delete_touching_from(board, row, col, board[row][col])

dropping = True
while dropping:
dropping = False
for r in range(len(board) - 1):
for col in range(len(board[row])):
row = len(board) - r - 1
if not board[row][col] and board[row - 1][col]:
board[row][col] = board[row - 1][col]
board[row - 1][col] = None
dropping = True
return points

def _delete_touching_from(self, board, row, col, color):
if row < 0 or col < 0 or row >= len(board) or col >= len(board[row]) or \
board[row][col] is None or board[row][col] != color:
return

current_color = board[row][col]
board[row][col] = None
if board[row][col] != 'neutral':
self._delete_touching_from(board, row - 1, col, color)
self._delete_touching_from(board, row, col - 1, color)
self._delete_touching_from(board, row + 1, col, color)
self._delete_touching_from(board, row, col + 1, color)


def _drop_pair(self, old_board, pair, cols):
board = [[color for color in row] for row in old_board]
self._drop_puyo(board, pair[1], cols[1])
self._drop_puyo(board, pair[0], cols[0])
return board

def _calculate_points(self, board):
visited = [[False for puyo in row] for row in board]
points = 0
for row in range(len(board)):
for col in range(len(board[row])):
color = board[row][col]
if color != 'neutral':
p = self._calculate_points_from(board, visited, row, col, color)
points += pow(p, 2)

return points

def _max_height(self, board):
for row in range(len(board)):
for puyo in board[row]:
if puyo:
return len(board) - row
return 0

def _calculate_points_from(self, board, visited, row, col, color):
if row < 0 or col < 0 or row >= len(board) or col >= len(board[row]) or \
board[row][col] is None or board[row][col] != color or visited[row][col]:
return 0

visited[row][col] = True
if board[row][col] == 'neutral':
return 1

return 1 + self._calculate_points_from(board, visited, row - 1, col, color) + \
self._calculate_points_from(board, visited, row, col - 1, color) + \
self._calculate_points_from(board, visited, row + 1, col, color) + \
self._calculate_points_from(board, visited, row, col + 1, color)

def _drop_puyo(self, board, puyo, col):
for r in range(len(board)):
row = len(board) - r - 1
if board[row][col] == None:
board[row][col] = puyo
return True
return False

def _clone_board(self):
board = self.board.empty_board()
for puyo in self.board.sprites():
if not puyo in self.board.current_pair:
board[puyo.row][puyo.col] = puyo.color
return board

10 changes: 7 additions & 3 deletions puyo/puyo.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ def __init__(self, parent, color, row = 0, col = 2):
width, height = parent.puyo_size
Movable.__init__(self, pygame.Rect(col * width, row * height, width, height), parent)
self.color = color
self.speed = 1
self.points = 10

def speed(self, fast):
return 15 if fast else 1

def get_row(self, y = None):
if y is None:
y = self.y
Expand All @@ -31,14 +33,16 @@ def same_color(self, other_puyo):
return self.color == other_puyo.color or other_puyo.color == 'neutral'

def __repr__(self):
return '<Puyo %s %d %d>' % (self.color, self.row, self.col)
return '<Puyo %s %d %d x:%d y:%d>' % (self.color, self.row, self.col, self.x, self.y)

class NeutralPuyo(Puyo):
def __init__(self, parent, row, col):
Puyo.__init__(self, parent, 'neutral', row, col)
self.speed = 15
self.points = 20

def same_color(self, other_puyo):
return False

def speed(self, fast):
return 15

4 changes: 2 additions & 2 deletions test/test_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def test_initializing(self):
assert_equal(self.subject.x, 10)
assert_equal(self.subject.y, 74)
assert_equal(self.subject.width, 64 * 6)
assert_equal(self.subject.height, 64 * 14)
assert_equal(self.subject.height, 64 * 15)
assert_equal(self.subject.puyo_size, (64, 64))
assert_equal(self.subject.current_pair, ())
assert_equal(self.subject.state, 'placing')
Expand Down Expand Up @@ -160,7 +160,7 @@ def test_reset_current_pair(self):
assert_equal(self.subject.state, 'scoring')

def test_reset_current_pair_if_dropped_on_other_puyo(self):
self.p2.y = self.subject.height - 65
self.p2.y = self.subject.height - 1
self.add_puyo('red', 13, 2)
self.subject.update()
assert_equal(self.subject.current_pair, ())
Expand Down
30 changes: 28 additions & 2 deletions test/test_player.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
from puyo.player import *
from puyo.board import *
from nose.tools import *
import pygame
pygame.init()

class TestPlayer():
pass
class TestAI():
def add_puyo(self, color, row, col):
self.board.add(Puyo(self.board, color, row, col))

def set_next_pair(self, color1, color2):
self.board.score.next_pair[0].color = color1
self.board.score.next_pair[1].color = color2

def setup(self):
self.board = Board(10, (10, 5), (32, 32))
self.add_puyo('yellow', 7, 0)
self.add_puyo('yellow', 8, 0)
self.add_puyo('yellow', 9, 0)

def test_ai_move(self):
self.set_next_pair('yellow', 'green')
player = ComputerPlayer(self.board)
self.set_next_pair('yellow', 'yellow')
player.next_move()
assert_equal(player.move, [2, 0, 19])

def test_ai_tries_to_minimize_height(self):
self.set_next_pair('green', 'red')
player = ComputerPlayer(self.board)
self.set_next_pair('green', 'red')
player.next_move()
assert_equal(player.move, [1, 0, -1])

4 changes: 2 additions & 2 deletions test/test_puyo.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_initializing(self):
assert_equal(self.puyo.x, 64)
assert_equal(self.puyo.image.get_width(), 32)
assert_equal(self.puyo.image.get_height(), 32)
assert_equal(self.puyo.speed, 1)
assert_equal(self.puyo.speed(False), 1)
assert_equal(self.puyo.points, 10)

def test_return_correct_row(self):
Expand Down Expand Up @@ -64,7 +64,7 @@ def setup(self):

def test_initializing(self):
assert_equal(self.puyo.color, 'neutral')
assert_equal(self.puyo.speed, 15)
assert_equal(self.puyo.speed(False), 15)
assert_equal(self.puyo.points, 20)

def test_same_color_with_none(self):
Expand Down

0 comments on commit 0744403

Please sign in to comment.