I have looked online everywhere and I am yet to find how to create a basic random dungeon generation technique.
I have been experimenting myself by randomising a list of numbers and allocating them to width height and position etc. but I just can't seem to get it working.
It would be great if someone could direct me to a website or maybe point me in the right direction, I am a little inexperienced. Thanks in advance.
Here's an example of a random dungeon generator, taken from RogueBasin, which has a lot of articles on that topic and on rogue-likes in general:
# Class to produce random map layouts
from random import *
from math import *
class dMap:
def __init__(self):
self.roomList=[]
self.cList=[]
def makeMap(self,xsize,ysize,fail,b1,mrooms):
"""Generate random layout of rooms, corridors and other features"""
# makeMap can be modified to accept arguments for values of failed, and percentile of features.
# Create first room
self.size_x = xsize
self.size_y = ysize
# initialize map to all walls
self.mapArr=[]
for y in range(ysize):
tmp = []
for x in range(xsize):
tmp.append(1)
self.mapArr.append( tmp )
w,l,t=self.makeRoom()
while len(self.roomList)==0:
y=randrange(ysize-1-l)+1
x=randrange(xsize-1-w)+1
p=self.placeRoom(l,w,x,y,xsize,ysize,6,0)
failed=0
while failed<fail: #The lower the value that failed< , the smaller the dungeon
chooseRoom=randrange(len(self.roomList))
ex,ey,ex2,ey2,et=self.makeExit(chooseRoom)
feature=randrange(100)
if feature<b1: #Begin feature choosing (more features to be added here)
w,l,t=self.makeCorridor()
else:
w,l,t=self.makeRoom()
roomDone=self.placeRoom(l,w,ex2,ey2,xsize,ysize,t,et)
if roomDone==0: #If placement failed increase possibility map is full
failed+=1
elif roomDone==2: #Possiblilty of linking rooms
if self.mapArr[ey2][ex2]==0:
if randrange(100)<7:
self.makePortal(ex,ey)
failed+=1
else: #Otherwise, link up the 2 rooms
self.makePortal(ex,ey)
failed=0
if t<5:
tc=[len(self.roomList)-1,ex2,ey2,t]
self.cList.append(tc)
self.joinCorridor(len(self.roomList)-1,ex2,ey2,t,50)
if len(self.roomList)==mrooms:
failed=fail
self.finalJoins()
def makeRoom(self):
"""Randomly produce room size"""
rtype=5
rwide=randrange(8)+3
rlong=randrange(8)+3
return rwide,rlong,rtype
def makeCorridor(self):
"""Randomly produce corridor length and heading"""
clength=randrange(18)+3
heading=randrange(4)
if heading==0: #North
wd=1
lg=-clength
elif heading==1: #East
wd=clength
lg=1
elif heading==2: #South
wd=1
lg=clength
elif heading==3: #West
wd=-clength
lg=1
return wd,lg,heading
def placeRoom(self,ll,ww,xposs,yposs,xsize,ysize,rty,ext):
"""Place feature if enough space and return canPlace as true or false"""
#Arrange for heading
xpos=xposs
ypos=yposs
if ll<0:
ypos+=ll+1
ll=abs(ll)
if ww<0:
xpos+=ww+1
ww=abs(ww)
#Make offset if type is room
if rty==5:
if ext==0 or ext==2:
offset=randrange(ww)
xpos-=offset
else:
offset=randrange(ll)
ypos-=offset
#Then check if there is space
canPlace=1
if ww+xpos+1>xsize-1 or ll+ypos+1>ysize:
canPlace=0
return canPlace
elif xpos<1 or ypos<1:
canPlace=0
return canPlace
else:
for j in range(ll):
for k in range(ww):
if self.mapArr[(ypos)+j][(xpos)+k]!=1:
canPlace=2
#If there is space, add to list of rooms
if canPlace==1:
temp=[ll,ww,xpos,ypos]
self.roomList.append(temp)
for j in range(ll+2): #Then build walls
for k in range(ww+2):
self.mapArr[(ypos-1)+j][(xpos-1)+k]=2
for j in range(ll): #Then build floor
for k in range(ww):
self.mapArr[ypos+j][xpos+k]=0
return canPlace #Return whether placed is true/false
def makeExit(self,rn):
"""Pick random wall and random point along that wall"""
room=self.roomList[rn]
while True:
rw=randrange(4)
if rw==0: #North wall
rx=randrange(room[1])+room[2]
ry=room[3]-1
rx2=rx
ry2=ry-1
elif rw==1: #East wall
ry=randrange(room[0])+room[3]
rx=room[2]+room[1]
rx2=rx+1
ry2=ry
elif rw==2: #South wall
rx=randrange(room[1])+room[2]
ry=room[3]+room[0]
rx2=rx
ry2=ry+1
elif rw==3: #West wall
ry=randrange(room[0])+room[3]
rx=room[2]-1
rx2=rx-1
ry2=ry
if self.mapArr[ry][rx]==2: #If space is a wall, exit
break
return rx,ry,rx2,ry2,rw
def makePortal(self,px,py):
"""Create doors in walls"""
ptype=randrange(100)
if ptype>90: #Secret door
self.mapArr[py][px]=5
return
elif ptype>75: #Closed door
self.mapArr[py][px]=4
return
elif ptype>40: #Open door
self.mapArr[py][px]=3
return
else: #Hole in the wall
self.mapArr[py][px]=0
def joinCorridor(self,cno,xp,yp,ed,psb):
"""Check corridor endpoint and make an exit if it links to another room"""
cArea=self.roomList[cno]
if xp!=cArea[2] or yp!=cArea[3]: #Find the corridor endpoint
endx=xp-(cArea[1]-1)
endy=yp-(cArea[0]-1)
else:
endx=xp+(cArea[1]-1)
endy=yp+(cArea[0]-1)
checkExit=[]
if ed==0: #North corridor
if endx>1:
coords=[endx-2,endy,endx-1,endy]
checkExit.append(coords)
if endy>1:
coords=[endx,endy-2,endx,endy-1]
checkExit.append(coords)
if endx<self.size_x-2:
coords=[endx+2,endy,endx+1,endy]
checkExit.append(coords)
elif ed==1: #East corridor
if endy>1:
coords=[endx,endy-2,endx,endy-1]
checkExit.append(coords)
if endx<self.size_x-2:
coords=[endx+2,endy,endx+1,endy]
checkExit.append(coords)
if endy<self.size_y-2:
coords=[endx,endy+2,endx,endy+1]
checkExit.append(coords)
elif ed==2: #South corridor
if endx<self.size_x-2:
coords=[endx+2,endy,endx+1,endy]
checkExit.append(coords)
if endy<self.size_y-2:
coords=[endx,endy+2,endx,endy+1]
checkExit.append(coords)
if endx>1:
coords=[endx-2,endy,endx-1,endy]
checkExit.append(coords)
elif ed==3: #West corridor
if endx>1:
coords=[endx-2,endy,endx-1,endy]
checkExit.append(coords)
if endy>1:
coords=[endx,endy-2,endx,endy-1]
checkExit.append(coords)
if endy<self.size_y-2:
coords=[endx,endy+2,endx,endy+1]
checkExit.append(coords)
for xxx,yyy,xxx1,yyy1 in checkExit: #Loop through possible exits
if self.mapArr[yyy][xxx]==0: #If joins to a room
if randrange(100)<psb: #Possibility of linking rooms
self.makePortal(xxx1,yyy1)
def finalJoins(self):
"""Final stage, loops through all the corridors to see if any can be joined to other rooms"""
for x in self.cList:
self.joinCorridor(x[0],x[1],x[2],x[3],10)
# ----------------------------------------------------------------------------
# Main
# ----------------------------------------------------------------------------
startx=20 # map width
starty=10 # map height
themap= dMap()
themap.makeMap(startx,starty,110,50,60)
for y in range(starty):
line = ""
for x in range(startx):
if themap.mapArr[y][x]==0:
line += "."
if themap.mapArr[y][x]==1:
line += " "
if themap.mapArr[y][x]==2:
line += "#"
if themap.mapArr[y][x]==3 or themap.mapArr[y][x]==4 or themap.mapArr[y][x]==5:
line += "="
print line
There are a lot of different methods to create a random dungeon, a common one is using Binary space partitioning (BSP).
Also make sure to take a look at libtcod, which offers also an implementation of that algorithm.
Related
Here is my code so far using random to pick moves:
import time
import random
PHealth = 10
CHealth = 10
PShots = [
"Great Body Shot To Your Opponent!",
"Nice Take Down!",
"Nice Punch!",
"Strong Kick!",
"You Have Him Pinned Against The Cage!",
"Excellent Counter-Shot!"
]
CShots = [
"You Took a Shot to the Body!",
"You Got Taken Down!",
"Strong Kick Hit You!",
"You Took A Big Punch!",
"You Are Pinned Against The Cage",
"Counter-Shot Got Ya!"
]
for i in range(20):
i = random.randint(0, 100)
if i >= 51:
print(random.choice(PShots))
CHealth = CHealth -1
if CHealth >= 1:
print("Player Health", PHealth)
print("Computer Health", CHealth)
time.sleep(5)
if i <= 50:
print(random.choice(CShots))
PHealth = PHealth -1
if PHealth >= 1:
print("Player Health", PHealth)
print("Computer Health", CHealth)
time.sleep(5)
if CHealth < 1:
print("What A Shot!")
time.sleep(1)
print("Down He Goes!")
time.sleep(1)
print("The Referee Has Stopped The Fight!!")
time.sleep(1)
print("Player Wins!!!")
break
if PHealth < 1:
print("What A Shot!")
time.sleep(1)
print("Down You Go!")
time.sleep(1)
print("The Referee Has Stopped The Fight!!")
time.sleep(1)
print("Computer Wins!!!")
break
Basically I'd like to understand how a player can input one move. So if a player inputs body shot it beats take down. If a player inputs kick it beats punch. If a player inputs take down it beats pinned against the cage, etc. Thinking 6-7 variations and counters.
Here is an idea of how you could implement something akin to what you seem to be looking for using a mix of classes and functions.
The following code should work with Python 3.9+ and has no additional dependencies.
First we define a Move class, instances of which need to have a name, a text_used (for when the player successfully uses the move), and a text_affected (for when move is used against the player). Each instance also stores a set of other Move objects, which it trumps, as well as a set of those it is trumped by. We have a helper method should_beat to easily define such a relationship between two moves.
class Move:
def __init__(self, name: str, text_used: str, text_affected: str, damage: int = 1) -> None:
self.name: str = name
self.text_used: str = text_used
self.text_affected: str = text_affected
self.damage: int = damage
self.trumps: set['Move'] = set()
self.trumped_by: set['Move'] = set()
def __str__(self) -> str:
return self.name
def should_beat(self, other_move: 'Move') -> None:
self.trumps.add(other_move)
other_move.trumped_by.add(self)
Next we define a Player class. Its instances have a name and an optional starting_health set to a previously defined constant by default.
A Player also has a use_move method that takes a Move object, another Player object (the opponent), and a second Move object (the move used by the opponent). That method checks which move beats which and calculates the health subtraction accordingly.
Finally, the Player object has win and lose methods that can be called to print out the win/loss statements when necessary.
class Player:
def __init__(self, name: str, starting_health: int = DEFAULT_STARTING_HEALTH) -> None:
self.name: str = name
self.health: int = starting_health
def __str__(self) -> str:
return self.name
def use_move(self, move: Move, vs_player: 'Player', vs_move: Move) -> None:
if vs_move in move.trumped_by:
self.health -= vs_move.damage
print(vs_move.text_affected)
elif move in vs_move.trumped_by:
vs_player.health -= move.damage
print(move.text_used)
else:
print(TEXT_NO_EFFECT)
def win(self, vs: 'Player') -> None:
print("What A Shot!")
sleep(1)
print(f"{vs} Goes Down!")
sleep(1)
print("The Referee Has Stopped The Fight!!")
sleep(1)
print(f"{self} Wins!!!")
def lose(self, vs: 'Player') -> None:
print("What A Shot!")
sleep(1)
print(f"Down You Go, {self}!")
sleep(1)
print("The Referee Has Stopped The Fight!!")
sleep(1)
print(f"{vs} Wins!!!")
Next, we want a function to define our moves and one that conveniently prints a list of moves to the terminal. Obviously you will want to expand the define_moves function to incorporate all your desired moves and their relationships. It should return a list of all your Move objects.
def define_moves() -> list[Move]:
kick = Move("kick", "Strong Kick!", "Strong Kick Hit You!")
punch = Move("punch", "Nice Punch!", "You Took A Big Punch!")
...
kick.should_beat(punch)
...
return [
kick,
punch,
]
def print_moves(moves_list: list[Move]) -> None:
print("Available moves:")
for i, move in enumerate(moves_list):
print(i, "-", move.name)
Now for the fun part, we need our main fighting loop. In each iteration, we prompt the player for a number that corresponds to an index in our list of moves defined earlier. (The player may also type h to see the moves again.) We do some checks to make sure we received a valid number to get our Move object.
Then we randomly chose a move for the computer opponent out of our moves list, and call our use_move method. It does the health calculations for us. So after that we just check, if someone is done to call the appropriate win or lose method and break out of the loop. Otherwise we print the current health stats and continue.
def fight_computer(player: Player, moves_list: list[Move]) -> None:
computer = Player(name="Computer")
while True:
string = input('Choose your move! (or type "h" to see available moves again)\n').strip().lower()
if string == 'h':
print_moves(moves_list)
continue
try:
i = int(string)
except ValueError:
print("You need to pick a number!")
continue
try:
move = moves_list[i]
except IndexError:
print("No move available with number", i)
continue
computer_move = choice(moves_list)
print(computer, "chose", computer_move)
player.use_move(move, vs_player=computer, vs_move=computer_move)
if player.health < 1:
player.lose(vs=computer)
break
if computer.health < 1:
player.win(vs=computer)
break
print(player, "health:", player.health)
print(computer, "health:", computer.health)
sleep(1)
Lastly, we need a main function to put it all together, prompt the player for his name, etc.
def main() -> None:
player_name = input("Enter your name: ").strip()
player = Player(player_name)
moves_list = define_moves()
print_moves(moves_list)
fight_computer(player, moves_list)
if __name__ == '__main__':
main()
Don't forget to add your imports and constant definitions to the start of the module:
from random import choice
from time import sleep
DEFAULT_STARTING_HEALTH = 10
TEXT_NO_EFFECT = "Your move had no effect!"
All together, this should give you a crude version of the game you described. Try it out. Hope this helps.
Here is the link to the codes and you can play around with the program:
https://py3.codeskulptor.org/#user306_KJotPenLg75Fx0e_6.py
import simplegui
import math
import codeskulptor
width,height=800,550
center=(width/2,height/2)
keys=0
pi=3.1415926535897932384626433832795
#Camera
cam_pos=[0,0,0]
cam_speed=5
rotate_speed=pi/90#radians
yaw,pitch=0,0
W,R,A,S,D,F,DOWN,UP,LEFT,RIGHT=False,False,False,False,False,False,False,False,False,False
#The distance between the camera and the canvas.
canvas_distance=400
#input a 3d point, return the projected position on the canvas
def projection(pos):
delta_x=-pos[0]
delta_y=-pos[1]
delta_z=-pos[2]
if delta_z==canvas_distance:return(center[0]-delta_x,center[1]+delta_y)
if 0<delta_z<canvas_distance:return (center[0]-(delta_x/delta_z)*canvas_distance,center[1]+(delta_y/delta_z)*canvas_distance)
if delta_z<=0:return (center[0]-delta_x*10000,center[1]+delta_y*10000)
ratio=canvas_distance/delta_z
return [center[0]-delta_x*ratio,center[1]+delta_y*ratio]
#rotate around the camera
def rotation(pos):
#yaw changes x and z
if LEFT==True or RIGHT==True:
cosine=math.cos(yaw)
sine=math.sin(yaw)
pos[2]=cosine*pos[2]+sine*pos[0]
pos[0]=cosine*pos[0]-sine*pos[2]
#pitch changes y and z
elif UP==True or DOWN==True:
cosine=math.cos(pitch)
sine=math.sin(pitch)
pos[1]=cosine*pos[1]+sine*pos[2]
pos[2]=cosine*pos[2]-sine*pos[1]
return pos
def cam_update():
global yaw,pitch
if W==True:translate(2,cam_speed)
elif S==True:translate(2,-cam_speed)
if A==True:translate(0,cam_speed)
elif D==True:translate(0,-cam_speed)
if R==True:translate(1,-cam_speed)
elif F==True:translate(1,+cam_speed)
if LEFT==True or RIGHT==True or UP==True or DOWN==True:
if LEFT==True:yaw=rotate_speed
elif RIGHT==True:yaw=-rotate_speed
if UP==True:pitch=rotate_speed
elif DOWN==True:pitch=-rotate_speed
rotate()
def keyup(key):
global W,R,A,S,D,F,DOWN,UP,LEFT,RIGHT,keys
keys-=1
if key==simplegui.KEY_MAP['W']:W=False
elif key==simplegui.KEY_MAP['S']:S=False
elif key==simplegui.KEY_MAP['A']:A=False
elif key==simplegui.KEY_MAP['D']:D=False
elif key==simplegui.KEY_MAP['R']:R=False
elif key==simplegui.KEY_MAP['F']:F=False
elif key==simplegui.KEY_MAP['up']:UP=False
elif key==simplegui.KEY_MAP['down']:DOWN=False
elif key==simplegui.KEY_MAP['left']:LEFT=False
elif key==simplegui.KEY_MAP['right']:RIGHT=False
def keydown(key):
global W,R,A,S,D,F,DOWN,UP,LEFT,RIGHT,keys
keys+=1
#move forward & backward
if key==simplegui.KEY_MAP['W']:W,S=True,False
elif key==simplegui.KEY_MAP['S']:S,W=True,False
#move left & right
elif key==simplegui.KEY_MAP['A']:A,D=True,False
elif key==simplegui.KEY_MAP['D']:D,A=True,False
#levitate & fall
elif key==simplegui.KEY_MAP['R']:R,F=True,False
elif key==simplegui.KEY_MAP['F']:F,R=True,False
#pitch
elif key==simplegui.KEY_MAP['up']:UP,DOWN=True,False
elif key==simplegui.KEY_MAP['down']:DOWN,UP=True,False
#yaw
elif key==simplegui.KEY_MAP['left']:LEFT,Right=True,False
elif key==simplegui.KEY_MAP['right']:RIGHT,LEFT=True,False
def find_distance(a,b):
if len(a)==2:return math.sqrt((a[0]-b[0])**2+(a[1]-b[1])**2)
return math.sqrt((a[0]-b[0])**2+(a[1]-b[1])**2+(a[2]-b[2])**2)
class Point:
def __init__(self,name,color,loc):
self.name=name
self.loc=loc#(x,y,z)
self.color=color
self.pos=projection(self.loc)#canvas position
#Declaring Point objects
x=Point('X','red',[800,-180,-1300])
y=Point('Y','blue',[-200,820,-1300])
z=Point('Z','green',[-200,-180,-300])
origin=Point('O','white',[-200,-180,-1300])
points=[x,y,z,origin]
#translate all points
def translate(axis,units):
for point in points:point.loc[axis]+=units
#rotate all points
def rotate():
for point in points:point.loc=rotation(point.loc)
#this function executes 60 times a second
def draw(canvas):
for point in points:
canvas.draw_line(origin.pos,point.pos,3,point.color)
canvas.draw_text(point.name+str([int(x) for x in point.loc]),point.pos,15,'white')
#if any keys are pressed, update points' locations
if keys>0:
cam_update()
for point in points:
point.pos=projection(point.loc)
canvas.draw_text('distance between Z and O: '+str(find_distance(z.loc,origin.loc)),[15,20],20,'yellow')
frame = simplegui.create_frame("SSS 3D", width, height)
frame.set_draw_handler(draw)
frame.set_keydown_handler(keydown)
frame.set_keyup_handler(keyup)
label=frame.add_label('Click the canvas first')
label=frame.add_label('To move: WASD RF')
label=frame.add_label('To yaw or pitch: arrow keys')
frame.start()
When I try to rotate, the distance between two points decreases. You can see that the axes in my example keep shrinking. It also looks like the angles between the axes are getting more and more acute.
Solved. Precision problem. I just need to reduce intermediary variables and return the value in one long calculation.
I spent the afternoon building a trap tile function for my textRPG after several questions on here I'm getting down to the actually implementing the function. iv adapted it from my EnemyTile function where it tracks is alive by if the enemy has hit points greater than zero. My TrapTile has an is_tripped function that is supposed to be true or false. i thought i had wrote the function to trip when the player enters the room but that isnt the case. how would i trip this TrapTile. Github repo
https://github.com/GusRobins60/AdventureGame.git
class TrapRoomTile(MapTile):
def __init__(self, x, y):
self.trap = items.Trap
r = random.randint(1,2)
if r == 1:
self.trap = items.PitFall()
self.trap.is_tripped()
self.set_text = "The floor in this hallway is unusually clean."
time.sleep(1)
self.tripped_text = "The open hole of a Pit Fall trap obstructs the tunnel."
else:
self.set_text = "Looks like more bare stone... "
super().__init__(x, y)
def modify_player(self,player):
if self.trap.is_tripped():
player.hp = player.hp - self.items.damage
print("You stumbled into a trap!")
time.sleep(1)
print("\nTrap does {} damage. You have {} HP remaining.".
format(self.items.damage, player.hp))
def intro_text(self):
text = self.tripped_text if self.trap.is_tripped() else self.set_text
time.sleep(0.1)
return text
The following code is a python sprinting game. It was posted as an answer to my previous post, by #mango You have to tap 'a' and 'd' as fast as you can to run 100 meters. However, there are a few bugs...
1) If you hold down 'a' and 'd' at the same time, the game is completed as fast as possible and defeats the point of the game.
2) Like in my previous post, I would like to incorporate a scoring system into this code, however, due to my level of skill and experience with python, unfortunately, I am unable to do so, and would appreciate and suggestions.
Many Thanks
Previous code:
import msvcrt
import time
high_score = 50
name = "no-one"
while True:
distance = int(0)
print("\n--------------------------------------------------------------")
print('\n\nWelcome to the 100m sprint, tap a and d rapidly to move!')
print('* = 10m')
print("\n**Current record: " + str(high_score) + "s, by: " + name)
print('\nPress enter to start')
input()
print('Ready...')
time.sleep(1)
print('GO!')
start_time = time.time()
while distance < 100:
k1 = msvcrt.getch().decode('ASCII')
if k1 == 'a':
k2 = msvcrt.getch().decode('ASCII')
if k2 == 'd':
distance += 1
if distance == 50:
print("* You're halfway there!")
elif distance % 10 == 0:
print('*')
fin_time = time.time() - start_time
fin_time = round(fin_time,2)
print('Well done you did it in...'+str(fin_time))
if fin_time < high_score:
print("Well done you've got a new high score ")
name = input("Please enter your name : ")
This is the code that I recieved as feedback, I have made a few changes, but I am struggling witht the bugs that I listed before.
1) If you hold down 'a' and 'd' at the same time, the game is completed as fast as possible and defeats the point of the game.
2) Like in my previous post, I would like to incorporate a scoring system into this code, however, due to my level of skill and experience with python, unfortunately, I am unable to do so, and would appreciate and suggestions.
Many Thanks
# these are the modules we'll need
import sys
import tkinter as tk
class SprintGame(object):
# this represents the distance we'll allow our player to run
# NOTE: the distance is in meters
distance = 100
# this represents the stride length of the players legs
# NOTE: basically how far the player can travel in one footstep
stride = 1.5
# this represents the last key the user has pressed
lastKey = ""
# this represents wether or not the race has been completed
completed = False
# this function initiates as soon as the "sprint" variable is defined
def __init__(self):
# create the tk window
self.root = tk.Tk()
# set the tk window size
self.root.geometry('600x400')
# set the tk window title
self.root.title("Sprinting Game")
# bind the keypress event to the self.keypress handler
self.root.bind('<KeyPress>', self.keypress)
# center the window
self.centerWindow(self.root)
# insert the components
self.insertComponents()
# initial distance notice
self.output("{0}m left to go!".format(self.distance))
# start out wonderful game
self.start()
# this function centers the window
def centerWindow(self, window):
window.update_idletasks()
# get the screen width
width = window.winfo_screenwidth()
# get the screen height
height = window.winfo_screenheight()
# get the screen size
size = tuple(int(_) for _ in window.geometry().split('+') [0].split('x'))
# get the screen's dimensions
x = (width / 2) - (size[0] / 2)
y = (height / 2) - (size[1] / 2)
# set the geometry
window.geometry("%dx%d+%d+%d" % (size + (x, y)))
# this function replaces the old text in the textbox with new text
def output(self, text = ""):
self.text.delete('1.0', tk.END)
self.text.insert("end", text)
# this function handles key presses inside the tkinter window
def keypress(self, event):
# get the key and pass it over to self.logic
self.logic(event.char)
# this function handles game logic
def logic(self, key):
# convert key to a lower case string
key = str(key).lower()
# let us know how far we've got left
if key == "l":
self.output("{0}m left to go!".format(self.distance))
# restart the race
if key == "r":
# clear the output box
self.text.delete('1.0', tk.END)
# reset the distance
self.distance = 100
# reset the stride
self.stride = 1.5
# reset the last key
self.lastKey = ""
# set race completed to false
self.completed = False
# output restart notice
self.output("The Race Has Been Restarted.")
# don't bother with logic if race is completed
if self.completed == True:
return False
# check if distance is less than or equal to zero (meaning the race is over)
if self.distance <= 0:
# set the "self.completed" variable to True
self.completed = True
# let us know we've completed the race
self.output("Well done, you've completed the race!")
# return true to stop the rest of the logic
return True
# convert the key to lower case
key = key.lower()
# this is the quit function
if key == "q":
# lights out...
sys.exit(0)
# check if the key is a
if key == "a":
# set the last key to a so that we can decrement the "distance"
# variable if it is pressed next
self.lastKey = "a"
# only bother with "d" keypresses if the last key was "a"
if self.lastKey == "a":
# capture the "d" keypress
if key == "d":
# decrement the "distance" variable
self.distance -= self.stride
# let us know how far into the game we are
self.output("{0}m left to go!".format(self.distance))
# this function inserts the components into the window
def insertComponents(self):
# this component contains all of our output
self.text = tk.Text(self.root, background='#d6d167', foreground='#222222', font=('Comic Sans MS', 12))
# lets insert out text component
self.text.pack()
# this function opens the window and starts the game
def start(self):
self.root.mainloop()
# create a new instance of the game
Game = SprintGame()
I'm working on an artificial intelligence program to play a board game. The trouble is that my implementation of the minimax algorithm has a serious error somehow that I've been trying to figure for a couple of days now and still haven't managed to figure it out.
It's doing my nut in so I'd appreciate it if someone could have a look at my code and see why it's not terminating properly.
When I run it it appears to correctly search the tree and terminate when it reaches the final depth but for some reason it can't search the of the deepest layer and forever finds more nodes to search (which is theoretically impossible).
I have covered all the rest of the code with unittests and it's all working. However my unittests on this part don't ever work because it just endlessly executes without terminating.
If there is any information that can help I can provide it.
Thanks
def recursive_minimax(self, board, depth, player_id):
#there are no more moves possible so the game is over
if board.get_available_moves(player_id)==[]:
return 1 if player_id == self.max_player else -1
#check to ensure correct depth has been passed into method
if depth>self.depth:
raise ValueError("Impossible value for depth")
#if max depth reached calculate estimated node value
if depth == 0:
return self.eval_func(board, player_id)
if player_id == self.max_player:
best_val = -999
moves = board.get_available_moves(player_id)
for move in moves:
new_board = board.make_move(move, player_id, True)
opposing_player = board.get_opposing_player(player_id)
val = self.recursive_minimax(new_board, depth - 1, opposing_player)
best_val = max(val, best_val)
else:
best_val = 999
moves = board.get_available_moves(player_id)
for move in moves:
#make copy of the board and play move
new_board = board.make_move(move, player_id, True)
opposing_player = board.get_opposing_player(player_id)
val = self.recursive_minimax(new_board, depth - 1, opposing_player)
best_val = min(val, best_val)
return best_val
Here is the unittest (forgive my sloppiness in the test code):
def test_recursive_minimax(self):
#self.skipTest("recursive error")
init_board = _make_board(5)
for tile_id in init_board.keys():
counters = choice(range(len(init_board[tile_id]["neighbours"])-1))
init_board[tile_id]["counters"] = counters if counters!= 0 else None
init_board[tile_id]["player"] = choice([0,1]) if counters!=0 else None
board=Board(init_board, 0)
strategy = Minimax(board)
for tile_id, tile in strategy.board.tiles():
player = 0 if tile["player"]==None else tile["player"]
new_board = strategy.board.make_move(tile_id, player)
value = strategy.recursive_minimax(new_board, 1,player)
self.assertIsNotNone(value)
self.assertTrue(0<=value<=1 or value in(999, -999))
for tile_id, tile in strategy.board.tiles():
player = 0 if tile["player"]==None else tile["player"]
new_board = strategy.board.make_move(tile_id, player)
value = strategy.recursive_minimax(new_board, 2,player)
self.assertIsNotNone(value)
self.assertTrue(0<=value<=1 or value in(999, -999))
for tile_id, tile in strategy.board.tiles():
player = 0 if tile["player"]==None else tile["player"]
new_board = strategy.board.make_move(tile_id, player)
value = strategy.recursive_minimax(new_board, 3,player)
self.assertIsNotNone(value)
self.assertTrue(0<=value<=1 or value in(999, -999))
the unittest never gets to the assertion because the minimax doesn't terminate