Skip to content

Commit

Permalink
Merge pull request #2 from JuliaReinforcementLearning/add_reset_method
Browse files Browse the repository at this point in the history
Add reset method
  • Loading branch information
findmyway committed Aug 23, 2020
2 parents ec4c51e + 3493733 commit 0adf0f3
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 42 deletions.
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ language: julia
notifications:
email: false
julia:
- 1.0
- 1.4
- 1.5
- nightly
os:
- linux
- osx
- windows
arch:
- x64
cache:
Expand All @@ -21,7 +19,7 @@ jobs:
- julia: nightly
include:
- stage: Documentation
julia: 1
julia: 1.5
script: |
julia --project=docs -e '
using Pkg
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "SnakeGames"
uuid = "34dccd9f-48d6-4445-aa0f-8c2e373b5429"
authors = ["Jun Tian <tianjun.cpp@gmail.com> and contributors"]
version = "0.1.1"
version = "0.1.2"

[deps]
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
Expand All @@ -11,7 +11,7 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
[compat]
julia = "1"
Colors = "0.9, 0.10, 0.11, 0.12"
Makie = "0.11"
Makie = "0.9,0.10,0.11"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand Down
23 changes: 20 additions & 3 deletions src/interaction.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
export init_screen, play
export play, scene_and_node

using Makie
using Colors

const SNAKE_GAME_SCEENS = IdDict()

function scene_and_node(game::SnakeGame)
if haskey(SNAKE_GAME_SCEENS, game)
SNAKE_GAME_SCEENS[game]
else
node = Node(game)
scene = init_screen(node)
get!(SNAKE_GAME_SCEENS, game, (scene, node))
end
end

function Base.display(game::SnakeGame)
scene, node = scene_and_node(game)
node[] = game
display(scene)
end

function init_screen(game::Observable{<:SnakeGame{2}}; resolution=(1000,1000))
SNAKE_COLORS = range(HSV(60,1,1), stop=HSV(300,1,1), length=length(game[].snakes)+1)
scene = Scene(resolution = resolution, raw = true, camera = campixel!)
Expand Down Expand Up @@ -39,8 +57,7 @@ play() = play(SnakeGame())

function play(game::SnakeGame{2};f_name="test.gif",framerate = 2)
@assert length(game.snakes) <= 3 "At most three players are supported in interactive mode"
game_node = Node(game)
scene = init_screen(game_node)
scene, game_node = scene_and_node(game)

LEFT = CartesianIndex(-1, 0)
RIGHT = CartesianIndex(1, 0)
Expand Down
80 changes: 47 additions & 33 deletions src/snake_game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,76 +3,90 @@ export SnakeGame
using Random

struct SnakeGame{N,M,R<:AbstractRNG}
# WHCN
board::BitArray{M}
# TODO: using DataStructures: Deque ?
snakes::Vector{Vector{CartesianIndex{N}}}
foods::Set{CartesianIndex{N}}
walls::Vector{CartesianIndex{N}}
n_foods::Int
rng::R
end

Base.size(g::SnakeGame) = size(g.board)[2:end]
Base.size(g::SnakeGame) = size(g.board)[1:end-1]

ind_of_wall(game) = 2*length(game.snakes)+1
ind_of_food(game) = 2*length(game.snakes)+2

mark_wall!(game, ind) = game.board[ind_of_wall(game), ind] = true
mark_food!(game, ind) = game.board[ind_of_food(game), ind] = true
unmark_food!(game, ind) = game.board[ind_of_food(game), ind] = false
mark_snake_head!(game, ind, i) = game.board[i, ind] = true
unmark_snake_head!(game, ind, i) = game.board[i, ind] = false
mark_snake_body!(game, ind, i) = game.board[length(game.snakes)+i, ind] = true
unmark_snake_body!(game, ind, i) = game.board[length(game.snakes)+i, ind] = false
mark_wall!(game, ind) = game.board[ind, ind_of_wall(game)] = true
mark_food!(game, ind) = game.board[ind, ind_of_food(game)] = true
unmark_food!(game, ind) = game.board[ind, ind_of_food(game)] = false
mark_snake_head!(game, ind, i) = game.board[ind, i] = true
unmark_snake_head!(game, ind, i) = game.board[ind, i] = false
mark_snake_body!(game, ind, i) = game.board[ind, length(game.snakes)+i] = true
unmark_snake_body!(game, ind, i) = game.board[ind, length(game.snakes)+i] = false

get_walls(game) = findall(isone, selectdim(game.board, 1, ind_of_wall(game)))
get_walls(game) = findall(isone, selectdim(game.board, ndims(game.board), ind_of_wall(game)))

"""
SnakeGame(;kwargs...)
# Keyword Arguments
- `size::NTuple{N,Int}=(8,8)`, the size of game board. `N` can be greater than 2.
- `shape::NTuple{N,Int}=(8,8)`, the size of game board. `N` can be greater than 2.
- `walls`, an iterable type with elements of type `CartesianIndex{N}`.
- `n_snakes`, number of snakes.
- `n_foods`, maximum number of foods in each step.
- `rng::AbstractRNG`, inner RNG used to sample initial snakes and necessary foods in each step.
"""
function SnakeGame(;size=(8,8), walls=[], n_snakes=1, n_foods=1,rng=Random.GLOBAL_RNG)
n_snakes+n_foods >= reduce(*, size) && error("n_snakes+n_foods must be less than the total grids")
board = BitArray(undef, n_snakes+n_snakes+1#=wall=#+1#=food=#, size...)
snakes = [Vector{CartesianIndex{length(size)}}() for _ in 1:n_snakes]
foods = Set{CartesianIndex{length(size)}}()
game = SnakeGame(board, snakes, foods, rng)
function SnakeGame(;shape=(8,8), walls=CartesianIndex{length(shape)}[], n_snakes=1, n_foods=1,rng=Random.GLOBAL_RNG)
n_snakes+n_foods >= reduce(*, shape) && error("n_snakes+n_foods must be less than the total grids")
board = BitArray(undef, shape..., n_snakes#=snake head=#+n_snakes#=snake body=#+1#=wall=#+1#=food=#)
snakes = [Vector{CartesianIndex{length(shape)}}() for _ in 1:n_snakes]
foods = Set{CartesianIndex{length(shape)}}()
game = SnakeGame(board, snakes, foods, walls, n_foods, rng)
reset!(game)
game
end

function reset!(game::SnakeGame)
fill!(game.board, false)

for s in game.snakes
empty!(s)
end

empty!(game.foods)

fill!(board, false)
# do not change walls

for w in walls
for w in game.walls
mark_wall!(game, w)
end

while length(foods) < n_foods
p = rand(rng, CartesianIndices(size))
if any(@view(board[:, p]))
while length(game.foods) < game.n_foods
p = rand(game.rng, CartesianIndices(size(game)))
if any(@view(game.board[p, :]))
continue # there's a wall
else
push!(foods, p)
push!(game.foods, p)
mark_food!(game, p)
end
end

for i in 1:n_snakes
for i in 1:length(game.snakes)
while true
p = rand(rng, CartesianIndices(size))
if any(@view(board[:, p]))
p = rand(game.rng, CartesianIndices(size(game)))
if any(@view(game.board[p, :]))
continue # there's a wall or food
else
push!(snakes[i], p)
push!(game.snakes[i], p)
mark_snake_head!(game, p, i)
break
end
end
end

game
end

(game::SnakeGame)(action::CartesianIndex) = game([action])
Expand All @@ -82,7 +96,7 @@ function (game::SnakeGame{N})(actions::Vector{CartesianIndex{N}}) where N
for ((i, s), a) in zip(enumerate(game.snakes), actions)
unmark_snake_head!(game, s[1], i)
mark_snake_body!(game, s[1], i)
pushfirst!(s, CartesianIndex(mod.((s[1] + a).I, axes(game.board)[2:end])))
pushfirst!(s, CartesianIndex(mod.((s[1] + a).I, axes(game.board)[1:end-1])))
mark_snake_head!(game, s[1], i)
if s[1] in game.foods
unmark_food!(game, s[1])
Expand All @@ -93,25 +107,25 @@ function (game::SnakeGame{N})(actions::Vector{CartesianIndex{N}}) where N
end
# 2. check collision
for s in game.snakes
sum(@view(game.board[:, s[1]])) == 1 || return false
sum(@view(game.board[s[1],:])) == 1 || return false
end
# 3. create new foods
for f in game.foods
if !game.board[ind_of_food(game), f]
if !game.board[f, ind_of_food(game)]
# food is eaten
pop!(game.foods, f)
food = rand(game.rng, CartesianIndices(size(game)))
attempts = 1
while any(@view(game.board[:, food]))
while any(@view(game.board[food, :]))
food = rand(game.rng, CartesianIndices(size(game)))
attempts += 1
if attempts > reduce(*, size(game))
@warn "a rare case happened: sampled too many times to generate food"
empty_positions = findall(iszero, vec(any(game.board, dims=1)))
empty_positions = findall(iszero, vec(any(game.board, dims=ndims(game.board))))
if length(empty_positions) == 0
return false
else
food = CartesianIndices(size(game.board)[2:end])[rand(game.rng, empty_positions)]
food = CartesianIndices(size(game))[rand(game.rng, empty_positions)]
break
end
end
Expand Down

2 comments on commit 0adf0f3

@findmyway
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/20016

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.1.2 -m "<description of version>" 0adf0f3b19cf537883c9dd792c9ec8aa7b23c4b5
git push origin v0.1.2

Please sign in to comment.