Skip to content

Commit 18da2ac

Browse files
committed
Merge branch 'init'
2 parents 8f1d991 + c1fa239 commit 18da2ac

File tree

4 files changed

+192
-3
lines changed

4 files changed

+192
-3
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ _Combat Turtles_ is meant as a learning tool for intermediate-level [Python](htt
88

99
The player can create their own turtle AIs by extending the `TurtleParent` class and overwriting a few key methods. The game is run using discrete step events (at a rate of approximately 30 steps/second), with each turtle defining its actions on a per-step basis. Custom AI submodules (in the form of standalone `.py` files) can be dropped into the `ai/` directory to import the player's AI into the game. Several example and template subclasses are included in this directory to get the player started. See also the [documentation below](#instructions) for a detailed guide to writing custom AIs. Python students might enjoy competing against each other to see whom can come up with the best AI, while Python instructors might consider running a class tournament to encourage students to learn more about object-oriented programming.
1010

11-
**This is a work in progress.** I am still in the process of adding features and fixing bugs, but if you are interested in playing with the latest public beta, please see the [releases](https://github.com/adam-rumpf/combat-turtles/releases) page.
11+
The latest release can be found on this project's [itch.io](https://adam-rumpf.itch.io/combat-turtles) page or on the [releases](https://github.com/adam-rumpf/combat-turtles/releases) page.
1212

1313
## Game Overview
1414

ai/wall.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Built-In Example AI
2+
3+
# Title: WallTurtle
4+
# Author: Adam Rumpf
5+
# Version: 1.0.0
6+
# Date: 1/5/2021
7+
8+
import math
9+
import game.tcturtle
10+
11+
class CombatTurtle(game.tcturtle.TurtleParent):
12+
"""Wall-hugging combat turtle.
13+
14+
A turtle that attempts to navigate around obstacles by "feeling" the walls
15+
around it.
16+
17+
When it has direct line of sight to the opponent, it moves directly
18+
towards it. Otherwise it moves towards the opponent until hitting a wall,
19+
at which point it attempts to turn so that the way ahead is free.
20+
"""
21+
22+
#-------------------------------------------------------------------------
23+
24+
def class_name():
25+
"""CombatTurtle.class_name() -> str
26+
Static method to return the name of the Combat Turtle AI.
27+
"""
28+
29+
return "WallTurtle"
30+
31+
#-------------------------------------------------------------------------
32+
33+
def class_desc():
34+
"""CombatTurtle.class_desc() -> str
35+
Static method to return a description of the Combat Turtle AI.
36+
"""
37+
38+
return "Hugs walls to get around obstacles."
39+
40+
#-------------------------------------------------------------------------
41+
42+
def class_shape():
43+
"""CombatTurtle.class_shape() -> (int or tuple)
44+
Static method to define the Combat Turtle's shape image.
45+
46+
The return value can be either an integer or a tuple of tuples.
47+
48+
Returning an integer index selects one of the following preset shapes:
49+
0 -- arrowhead (also default in case of unrecognized index)
50+
1 -- turtle
51+
2 -- plow
52+
3 -- triangle
53+
4 -- kite
54+
5 -- pentagon
55+
6 -- hexagon
56+
7 -- star
57+
58+
A custom shape can be defined by returning a tuple of the form
59+
(radius, angle), where radius is a tuple of radii and angle is a tuple
60+
of angles (in radians) describing the polar coordinates of a polygon's
61+
vertices. The shape coordinates should be given for a turtle facing
62+
east.
63+
"""
64+
65+
return 2
66+
67+
#=========================================================================
68+
69+
def setup(self):
70+
"""CombatTurtle.setup() -> None
71+
Initialization code for Combat Turtle.
72+
"""
73+
74+
# Define the relative polar coordinates around the turtle to scan
75+
self.nose_rel = (8, 0.0) # just ahead of turtle's front
76+
self.hand_rel = (8, math.pi/2) # to left of turtle
77+
78+
#-------------------------------------------------------------------------
79+
80+
def step(self):
81+
"""CombatTurtle.setup() -> None
82+
Step event code for Combat Turtle.
83+
"""
84+
85+
# Determine behavior based on whether there is line of sight
86+
if (self.line_of_sight() == True):
87+
# If there is line of sight, move directly towards opponent
88+
89+
# Turn towards opponent
90+
self.turn_towards()
91+
92+
# Move towards opponent (or away if too close)
93+
if self.distance() > 4*self.missile_radius:
94+
self.forward()
95+
else:
96+
self.backward()
97+
98+
# Shoot if facing opponent
99+
if (self.can_shoot == True and
100+
abs(self.relative_heading_towards()) <= 10):
101+
self.shoot()
102+
103+
else:
104+
# If no line of sight, attempt to navigate around obstacles
105+
106+
# Calculate Cartesian coordinates of nose and hand
107+
nose = ((self.x + self.nose_rel[0]*
108+
math.cos(math.radians(self.heading)+self.nose_rel[1])),
109+
(self.y - self.nose_rel[0]*
110+
math.sin(math.radians(self.heading)+self.nose_rel[1])))
111+
hand = ((self.x + self.hand_rel[0]*
112+
math.cos(math.radians(self.heading)+self.hand_rel[1])),
113+
(self.y - self.hand_rel[0]*
114+
math.sin(math.radians(self.heading)+self.hand_rel[1])))
115+
116+
# Determine behavior based on whether nose and hand are clear
117+
if self.free_space(nose) == True:
118+
# Move forward when clear ahead
119+
self.forward()
120+
else:
121+
if self.free_space(hand) == True:
122+
# If free to left, turn left
123+
self.left()
124+
self.forward()
125+
else:
126+
# If blocked ahead and to left, turn right
127+
self.right()
128+
self.forward()

game/obj/arena.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Defines the arena container class."""
22

33
import math
4+
import random
45
from .block import Block
56

67
class Arena:
@@ -16,6 +17,7 @@ class Arena:
1617
2 -- four columns near corners
1718
3 -- wall with a central passage
1819
4 -- plus sign
20+
5 -- randomized
1921
"""
2022

2123
#-------------------------------------------------------------------------
@@ -26,7 +28,7 @@ def get_names():
2628
"""
2729

2830
return ["Empty Arena", "Central Column Arena", "Corner Column Arena",
29-
"Doorway Arena", "Plus-Shaped Arena"]
31+
"Doorway Arena", "Plus-Shaped Arena", "Randomized Arena"]
3032

3133
#-------------------------------------------------------------------------
3234

@@ -117,6 +119,9 @@ def __init__(self, game, size=(800, 800), layout=0):
117119
elif layout == 4:
118120
# Plus sign
119121
self._plus_blocks()
122+
elif layout == 5:
123+
# Randomized
124+
self._random_blocks()
120125

121126
#-------------------------------------------------------------------------
122127

@@ -186,6 +191,62 @@ def _plus_blocks(self):
186191
self._blocks.append(Block(self.game, math.floor(self.size[0]/3),
187192
math.ceil(2*self.size[0]/3),
188193
(self.size[1]/2)-30, (self.size[1]/2)+30))
194+
195+
#-------------------------------------------------------------------------
196+
197+
def _random_blocks(self):
198+
"""Arena._random_blocks() -> None
199+
Generates a randomized arena.
200+
201+
The randomized layout is made up of 3-7 blocks, arranged to have
202+
2-fold rotational symmetry about the center, and prohibited from
203+
intersecting the turtles' starting coordinates.
204+
"""
205+
206+
# Initialize a random block height and width
207+
h = random.randrange(10, 151)
208+
w = random.randrange(10, 151)
209+
210+
# Decide whether to include a central block
211+
if random.random() < 0.5:
212+
213+
# Add central block
214+
self._blocks.append(Block(self.game, (self.size[0]/2)-w,
215+
(self.size[0]/2)+w, (self.size[1]/2)-h,
216+
(self.size[1]/2)+h))
217+
218+
# Determine number of additional blocks on sides
219+
num = random.randrange(1, 4)
220+
221+
# Generate side blocks
222+
iter = 0 # iteration counter
223+
while iter < num:
224+
225+
# Generate random dimensions and centers
226+
h = random.randrange(10, 121)
227+
w = random.randrange(10, 121)
228+
cx = random.randrange(self.size[0]+1)
229+
cy = random.randrange(self.size[1]+1)
230+
231+
# Generate tentative blocks
232+
self._blocks.append(Block(self.game, cx-w, cx+w, cy-h, cy+h))
233+
self._blocks.append(Block(self.game, self.size[0]-cx-w,
234+
self.size[0]-cx+w, self.size[1]-cy-h,
235+
self.size[1]-cy+h))
236+
237+
# Test whether the starting coordinates are free
238+
if (self.blocked(self.get_p1_coords()) or
239+
self.blocked(self.get_p2_coords())):
240+
241+
# If not, delete the tentative blocks and retry
242+
del self._blocks[-1]
243+
del self._blocks[-1]
244+
continue
245+
246+
else:
247+
248+
# Otherwise increment the counter
249+
iter += 1
189250

190251
#-------------------------------------------------------------------------
191252

game/obj/block.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __init__(self, game, left, right, bottom, top, col="black"):
2828
Requires the following positional arguments:
2929
game (tcgame.TurtleCombatGame) -- game driver object
3030
left (int) -- smallest x-coordinate (px)
31-
right (int) -- lartst x-coordinate (px)
31+
right (int) -- largest x-coordinate (px)
3232
bottom (int) -- smallest y-coordinate (px)
3333
top (int) -- largest y-coordinate (px)
3434

0 commit comments

Comments
 (0)