From b08f8f3eeab606433d11def027e785412ee9b009 Mon Sep 17 00:00:00 2001 From: Jun Tian Date: Thu, 20 Aug 2020 16:15:17 +0800 Subject: [PATCH 1/8] support reset --- src/snake_game.jl | 49 ++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/snake_game.jl b/src/snake_game.jl index ff3bea5..81fae72 100644 --- a/src/snake_game.jl +++ b/src/snake_game.jl @@ -7,6 +7,8 @@ struct SnakeGame{N,M,R<:AbstractRNG} # TODO: using DataStructures: Deque ? snakes::Vector{Vector{CartesianIndex{N}}} foods::Set{CartesianIndex{N}} + walls::Vector{CartesianIndex{N}} + n_foods::Int rng::R end @@ -30,49 +32,60 @@ get_walls(game) = findall(isone, selectdim(game.board, 1, ind_of_wall(game))) # 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, n_snakes#=snake head=#+n_snakes#=snake body=#+1#=wall=#+1#=food=#, shape...) + 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.board)[2:end])) + 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.board)[2:end])) + 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]) From 804042e291a802a4257478ffd8aa3d2ece8e8ff5 Mon Sep 17 00:00:00 2001 From: Jun Tian Date: Thu, 20 Aug 2020 18:26:49 +0800 Subject: [PATCH 2/8] expose scene_and_node --- src/interaction.jl | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/interaction.jl b/src/interaction.jl index 4b080b1..5bacdfe 100644 --- a/src/interaction.jl +++ b/src/interaction.jl @@ -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!) @@ -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) From 20f4a209ca583ee8a04e1ce374fe2d0735cbcbef Mon Sep 17 00:00:00 2001 From: Jun Tian Date: Thu, 20 Aug 2020 18:47:23 +0800 Subject: [PATCH 3/8] change CWHN to WHCN --- src/snake_game.jl | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/snake_game.jl b/src/snake_game.jl index 81fae72..2a95511 100644 --- a/src/snake_game.jl +++ b/src/snake_game.jl @@ -3,6 +3,7 @@ export SnakeGame using Random struct SnakeGame{N,M,R<:AbstractRNG} + # WHCN board::BitArray{M} # TODO: using DataStructures: Deque ? snakes::Vector{Vector{CartesianIndex{N}}} @@ -12,20 +13,20 @@ struct SnakeGame{N,M,R<:AbstractRNG} 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...) @@ -40,7 +41,7 @@ get_walls(game) = findall(isone, selectdim(game.board, 1, ind_of_wall(game))) """ 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, n_snakes#=snake head=#+n_snakes#=snake body=#+1#=wall=#+1#=food=#, shape...) + 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) @@ -64,8 +65,8 @@ function reset!(game::SnakeGame) end while length(game.foods) < game.n_foods - p = rand(game.rng, CartesianIndices(size(game.board)[2:end])) - if any(@view(game.board[:, p])) + p = rand(game.rng, CartesianIndices(size(game))) + if any(@view(game.board[p, :])) continue # there's a wall else push!(game.foods, p) @@ -75,8 +76,8 @@ function reset!(game::SnakeGame) for i in 1:length(game.snakes) while true - p = rand(game.rng, CartesianIndices(size(game.board)[2:end])) - if any(@view(game.board[:, p])) + p = rand(game.rng, CartesianIndices(size(game))) + if any(@view(game.board[p, :])) continue # there's a wall or food else push!(game.snakes[i], p) @@ -95,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]) @@ -106,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 From 503cbbf2b8d5d45ca04b8a48572c2b8de8bb8796 Mon Sep 17 00:00:00 2001 From: Jun Tian Date: Sat, 22 Aug 2020 23:40:10 +0800 Subject: [PATCH 4/8] add lower version of Makie --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 8274105..bad4aa2 100644 --- a/Project.toml +++ b/Project.toml @@ -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" From 3465d1e21a3927a1da56bbd48e247cafe187ba43 Mon Sep 17 00:00:00 2001 From: Jun Tian Date: Sat, 22 Aug 2020 23:53:05 +0800 Subject: [PATCH 5/8] test on 1.5 --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8669d98..4a90008 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,7 @@ language: julia notifications: email: false julia: - - 1.0 - - 1.4 + - 1.5 - nightly os: - linux From 50e52e100a8a0d3d059d6566d469d15060b75ce0 Mon Sep 17 00:00:00 2001 From: Jun Tian Date: Sat, 22 Aug 2020 23:53:36 +0800 Subject: [PATCH 6/8] update travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4a90008..e8a287e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ jobs: - julia: nightly include: - stage: Documentation - julia: 1 + julia: 1.5 script: | julia --project=docs -e ' using Pkg From dc90fcf12aec4e2628e8c65aaeb01f8ad84a11d5 Mon Sep 17 00:00:00 2001 From: Jun Tian Date: Sat, 22 Aug 2020 23:55:17 +0800 Subject: [PATCH 7/8] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index bad4aa2..c6baeb3 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SnakeGames" uuid = "34dccd9f-48d6-4445-aa0f-8c2e373b5429" authors = ["Jun Tian and contributors"] -version = "0.1.1" +version = "0.1.2" [deps] Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" From 3493733d1bec8ea8781e41fa8d85625db455c8db Mon Sep 17 00:00:00 2001 From: Jun Tian Date: Sun, 23 Aug 2020 11:31:11 +0800 Subject: [PATCH 8/8] ignore error on windows --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e8a287e..9633a58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ julia: os: - linux - osx - - windows arch: - x64 cache: