Permalink
Browse files

Added Tcl starter bot

  • Loading branch information...
1 parent 924a695 commit 0d0aa0f1c32fee3dc05b1e3d5401f048dba2be0e @paulwal paulwal committed Oct 28, 2011
View
67 ants/dist/starter_bots/tcl/MyBot.tcl
@@ -0,0 +1,67 @@
+# Ants, aichallenge.org
+# This file implements simple gameplay logic.
+# by paulwal
+
+source ants.tcl ;# This file handles the input, output, and game state. The procedure inputLoop must be called at the end of this file to start the input loop.
+source helper.tcl ;# Some helper procedures.
+
+
+# This procedure is called after "ready" is received from the game server.
+# A "go" message is automatically sent after this procedure returns.
+proc initialize {} {
+ return
+}
+
+# This is called after "end" is received from the server to indicate that the game has ended.
+proc endGame {} {
+ return
+}
+
+# This is called after the state of visible tiles for this turn has been received.
+# A "go" message is automatically sent after this procedure returns.
+proc doTurn {} {
+
+ # Loop through each ant.
+ foreach index [getMyAnts] {
+
+ # The 'index' is a two-element list of row and col values for a tile.
+ set row [lindex $index 0]
+ set col [lindex $index 1]
+
+
+ # Check adjacent tiles in this order: north, east, south, west.
+ foreach {direction offsetRow offsetCol} {N -1 0 E 0 1 S 1 0 W 0 -1} {
+
+
+ # This is the index of the adjacent tile.
+ incr offsetRow $row
+ incr offsetCol $col
+ set index "$offsetRow $offsetCol"
+
+
+ # The index could be out of bounds, so allow for wrap-around map edges.
+ set index [wrapIndex $index]
+
+
+ # If this adjacent tile is not water, attempt to move there and continue with the next ant.
+ if { [isPassable $index] } {
+
+ # Instruct the game server to move ant from $row,$col in this direction.
+ output "o $row $col $direction"
+
+ # Uncomment this line to see some debug info:
+ #puts stderr "turn $::Parameter(turn), moving ant to $index"
+
+ # Next!
+ break
+ }
+ }
+ }
+
+
+ return
+}
+
+
+# The input loop must be started.
+inputLoop
View
15 ants/dist/starter_bots/tcl/README.md
@@ -0,0 +1,15 @@
+## Tcl Package
+
+This is an AI Challenge starter bot written in Tcl. It requires tclsh 8.5.
+
+[Start Bot Guide on AI Challenge wiki](https://github.com/aichallenge/aichallenge/wiki/Ants-Starter-Pack-Guide)
+[Official Tcl website](http://tcl.tk)
+
+
+## Source
+
+`ants.tcl` maintains the game state and handles input and output.
+
+`helper.tcl` contains a few simple, helpful procedures.
+
+`MyBot.tcl` should consist of the bot's gameplay logic. Therein are three required procedures: initialize, doTurn, and endGame.
View
212 ants/dist/starter_bots/tcl/ants.tcl
@@ -0,0 +1,212 @@
+# Ants, aichallenge.org
+# This file handles the input, output, and game state.
+# by paulwal
+
+
+# This is a dictionary of the game board's state.
+# It stores visible and remembered tiles on the game board.
+# Each tile is indexed by the row and column as a two-element list: "$row $col".
+# The value of each tile in the dict is a nested dictionary of attributes(type, owner, blocked, l̶a̶s̶t̶s̶e̶e̶n̶) where 'blocked' may equal 1 if the type is a hill with an ant on it.
+# If there is no owner, for instance for a food tile, then -1 is used for the owner.
+set Tile [dict create]
+
+# This is an array of the game's parameters: like loadtime, turntime, viewradius2, turn number, etc.
+array set Parameter [list]
+
+
+# Output a line to the parent process, ie., the game server.
+proc output {line} {
+ puts stdout $line
+ flush stdout
+ return
+}
+
+# Continuously accept input from the server.
+proc inputLoop {} {
+
+ # Loop until the process is killed.
+ while 1 {
+
+ # Read one line from standard input.
+ gets stdin line
+
+ # Decided what to do with this input.
+ switch -glob -- $line {
+
+ "ready" { # Initialize.
+ # This procedure should be defined elsewhere.
+ initialize
+ output "go"
+ }
+
+ "go" { # Do turn.
+ # This procedure should be defined elsewhere.
+ doTurn
+
+ # End the turn.
+ output "go"
+
+ # Unset any tiles that are not water.
+ unsetNonWaterTiles
+ }
+
+ "end" { # The game is over.
+ # This procedure should be defined elsewhere.
+ endGame
+ }
+
+ default { # Adjust game state.
+ processInput $line
+ }
+ }
+ }
+
+ return
+}
+
+
+# Process input from the server.
+proc processInput {line} {
+ global Parameter
+
+ # The type of message.
+ set type [lindex $line 0]
+
+ # A list of values included in the message, or a single value if only one is present.
+ set value [lrange $line 1 end]
+
+ # For messages related to the state of tiles on the game board, row and col are the first two values,
+ # and for some messages 'owner' is a third value.
+ set row [lindex $value 0]
+ set col [lindex $value 1]
+ set owner [lindex $value 2]
+
+ switch -- $type {
+ "w" { setTile $row $col w -1 ;# Water tile. }
+ "f" { setTile $row $col f -1 ;# Food location. }
+ "h" { setTile $row $col h $owner ;# Hill location. }
+ "a" { setTile $row $col a $owner ;# An ant is here. }
+ "d" { setTile $row $col d $owner ;# An ant died here last turn. }
+ default { set Parameter($type) $value ;# This is a game parameter. }
+ }
+
+ return
+}
+
+
+proc unsetTile {index} {
+ global Tile
+ dict unset Tile $index
+ return
+}
+
+
+# Define the tile at a row and column.
+# An existing row,col tile will be overwritten, unless an ant and a hill are in the same tile position, in which case it will be saved as a hill that is blocked.
+proc setTile {row col type owner} {
+ global Tile
+
+ # Create a dict for this tile's attributes.
+ set attributes [dict create \
+ type $type \
+ owner $owner \
+ blocked 0 \
+ lastseen 0 \
+ ]
+
+ set index "$row $col"
+
+ # Check if this is a blocked hill.
+ # If this tile is being set to a hill or an ant... Then check if there is already a hill or ant on this tile f̶r̶o̶m̶ ̶t̶h̶i̶s̶ ̶t̶u̶r̶n̶.
+ if { $type == "h" || $type == "a" } {
+
+ # Check if this tile exists in the Matrix.
+ if { [dict exists $Tile $index] } {
+
+ # Check if it is an ant or hill type.
+ if { [dict get $Tile $index type] == "a" || [dict get $Tile $index type] == "h" } {
+
+ # This is a blocked hill! (ant+hill on the same tile)
+ dict set attributes type "h"
+ dict set attributes blocked 1
+ }
+ }
+ }
+
+ # Nest the tile dict in the GameState dict.
+ dict set Tile $index $attributes
+
+ return
+}
+
+
+# Get one or all attributes of a tile.
+# getTile "row col" ?attribute?
+# Returns the requested attribute or all attributes as a key-value list.
+# Attributes: type, owner, blocked, l̶a̶s̶t̶s̶e̶e̶n
+# type = w, f, h, a, or d (water, food, hill, ant, or dead ant)
+# owner = ID of player if type is h, a, or d. -1 otherwise. 0 = self
+# blocked = 1, if the type is a hill with an ant on it. 0 otherwise.
+# l̶a̶s̶t̶s̶e̶e̶n̶ ̶=̶ ̶T̶h̶e̶ ̶n̶u̶m̶b̶e̶r̶ ̶o̶f̶ ̶t̶u̶r̶n̶s̶ ̶a̶g̶o̶ ̶t̶h̶i̶s̶ ̶t̶i̶l̶e̶ ̶w̶a̶s̶ ̶s̶e̶e̶n̶,̶ ̶0̶ ̶=̶ ̶t̶h̶i̶s̶ ̶t̶u̶r̶n̶.̶ ̶T̶h̶i̶s̶ ̶i̶s̶ ̶u̶s̶e̶d̶ ̶t̶o̶ ̶t̶r̶a̶c̶k̶ ̶w̶h̶e̶n̶ ̶a̶ ̶t̶i̶l̶e̶ ̶w̶a̶s̶ ̶l̶a̶s̶t̶ ̶s̶e̶e̶n̶ ̶t̶o̶ ̶a̶s̶s̶e̶s̶s̶ ̶h̶o̶w̶ ̶a̶c̶c̶u̶r̶a̶t̶e̶ ̶t̶h̶a̶t̶ ̶i̶n̶f̶o̶r̶m̶a̶t̶i̶o̶n̶ ̶i̶s̶ ̶l̶i̶k̶e̶l̶y̶ ̶t̶o̶ ̶b̶e̶.̶ ̶A̶ ̶f̶o̶o̶d̶ ̶t̶i̶l̶e̶ ̶s̶e̶e̶n̶ ̶t̶h̶i̶s̶ ̶t̶u̶r̶n̶ ̶i̶s̶ ̶d̶e̶f̶i̶n̶i̶t̶e̶l̶y̶ ̶t̶h̶e̶r̶e̶,̶ ̶b̶u̶t̶ ̶o̶n̶e̶ ̶s̶e̶e̶n̶ ̶f̶i̶v̶e̶ ̶t̶u̶r̶n̶s̶ ̶a̶g̶o̶ ̶m̶a̶y̶ ̶b̶e̶ ̶g̶o̶n̶e̶ ̶b̶y̶ ̶n̶o̶w̶.̶
+proc getTile {index args} {
+ global Tile
+
+ # If an attribute is not provided, all attributes will be returned as a key-value list.
+ if { [dict exists $Tile $index {*}$args] } {
+ return [dict get $Tile $index {*}$args]
+ }
+
+ return
+}
+
+
+# Returns a list of indices for all tiles that match the given key-value pairs provided.
+# An operator is optional to use something other than the default equals operator (==).
+# Example:
+# getTiles {type h} {owner != 0}
+proc getTiles {args} {
+ global Tile
+
+ set indices [list]
+
+ dict for {index attributes} $Tile {
+ set flag 0
+
+ foreach comparison $args {
+ set key [lindex $comparison 0]
+ set value [lindex $comparison end]
+ set operator "=="
+
+ if { [llength $comparison] == 3 } {
+ set operator [lindex $comparison 1]
+ }
+
+ set bool [expr \"[dict get $attributes $key]\" $operator \"$value\"]
+
+ if $bool {
+ set flag 1
+
+ } else {
+ set flag 0
+ break
+ }
+ }
+
+ if { $flag == 1 } {
+ lappend indices $index
+ }
+ }
+
+ return $indices
+}
+
+
+# Unset any tile that isn't water.
+proc unsetNonWaterTiles {} {
+
+ foreach index [getTiles {type != w}] {
+ unsetTile $index
+ }
+
+ return
+}
View
115 ants/dist/starter_bots/tcl/helper.tcl
@@ -0,0 +1,115 @@
+# Here are some simple example helper procedures.
+
+proc getMyHills {} {
+ return [getTiles {type h} {owner 0}]
+}
+
+proc getEnemyHills {} {
+ return [getTiles {type h} {owner != 0}]
+}
+
+proc getMyAnts {} {
+ # Get all ants that belong to me.
+ set ants [getTiles {type a} {owner 0}]
+
+ # Get my blocked hills, which are an ant and a hill on the same tile but stored as hills with the blocked flag on.
+ lappend ants {*}[getTiles {blocked 1} {owner 0}]
+
+ return $ants
+}
+
+proc getEnemyAnts {} {
+ set ants [getTiles {type a} {owner != 0}]
+ lappend ants {*}[getTiles {blocked 1} {owner != 0}]
+
+ return $ants
+}
+
+proc getFoods {} {
+ return [getTiles {type f}]
+}
+
+# Returns 1 if the specified tile is not water.
+proc isPassable {index} {
+
+ if { [getTile $index type] == "w" } {
+ return 0
+ }
+
+ return 1
+}
+
+
+# Returns 1 or 0, indicating whether the provided tile is currently visible.
+# This isn't accurate for water tiles but it is very efficient.
+proc isVisible {index} {
+ global Tile
+
+ if { [dict exists $Tile $index] } {
+ return 1
+ }
+
+ return 0
+}
+
+
+# Returns a list of tile indices that are visible to an ant located at the provided xy coordinate.
+proc getVisibleIndices {index} {
+ global Parameter
+
+ set row [lindex $index 0] ;# The row of the tile index.
+ set col [lindex $index 1] ;# The column of the tile index.
+ set indices [list] ;# A list of visible indices which this procedure will return.
+ set radius2 $Parameter(viewradius2) ;# The view radius squared, as provided by the server.
+ set radius [expr { int(sqrt($radius2)) }] ;# The view radius, rounded to an integer towards zero.
+
+ # Start with a negative radius and loop through to a positive radius, for both row and column.
+ for {set offsetRow -$radius} {$offsetRow <= $radius} {incr offsetRow} {
+ for {set offsetCol -$radius} {$offsetCol <= $radius} {incr offsetCol} {
+
+ # Pythagorean's forumla. This is the distance the offset row,col is from 0,0. Ie., the hypotenuse squared.
+ set distance [expr { pow($offsetRow,2) + pow($offsetCol,2) }]
+
+ # I don't remember why this works. My brain hurts.
+ if { $distance <= $radius2 } {
+
+ # The original row,col plus this offset of each is visible.
+ set visibleRow [expr { $row+$offsetRow }]
+ set visibleCol [expr { $col+$offsetCol }]
+
+ # Allow the index to wrap around when out of bounds.
+ set index [wrapIndex "$visibleRow $visibleCol"]
+
+ lappend indices $index
+ }
+ }
+ }
+
+ return $indices
+}
+
+
+# If row or col are out of range for the map, adjust them so they wrap around.
+proc wrapIndex {index} {
+ global Parameter
+
+ set row [lindex $index 0]
+ set col [lindex $index 1]
+
+ if { $row < 0 } {
+ incr row $Parameter(rows)
+ }
+ if { $col < 0 } {
+ incr col $Parameter(cols)
+ }
+ if { $row >= $Parameter(rows) } {
+ incr row -$Parameter(rows)
+ }
+ if { $col >= $Parameter(cols) } {
+ incr col -$Parameter(cols)
+ }
+
+ return "$row $col"
+}
+
+
View
5 worker/compiler.py
@@ -480,6 +480,11 @@ def compile(self, bot_dir, globs, errors, timelimit):
[],
[(["*.ss"], ChmodCompiler("Scheme"))]
),
+ Language("Tcl", BOT +".tcl", "MyBot.tcl",
+ "tclsh MyBot.tcl",
+ [],
+ [(["*.tcl"], ChmodCompiler("Tcl"))]
+ ),
)

0 comments on commit 0d0aa0f

Please sign in to comment.