mirror of
https://github.com/Refactorio/RedMew.git
synced 2025-01-30 04:30:58 +02:00
snake
This commit is contained in:
parent
dd0930ce21
commit
3cfbecca17
18
features/snake/control.lua
Normal file
18
features/snake/control.lua
Normal file
@ -0,0 +1,18 @@
|
||||
local GameGui = require 'features.snake.gui'
|
||||
local Game = require 'features.snake.game'
|
||||
|
||||
local Public = {}
|
||||
|
||||
function Public.start_game(surface, top_left_position, size, update_rate, max_food)
|
||||
Game.start_game(surface, top_left_position, size, update_rate, max_food)
|
||||
GameGui.show()
|
||||
end
|
||||
|
||||
function Public.end_game()
|
||||
Game.end_game()
|
||||
GameGui.destroy()
|
||||
end
|
||||
|
||||
remote.add_interface('snake', Public)
|
||||
|
||||
return Public
|
376
features/snake/game.lua
Normal file
376
features/snake/game.lua
Normal file
@ -0,0 +1,376 @@
|
||||
local Global = require 'utils.global'
|
||||
local Task = require 'utils.task'
|
||||
local Token = require 'utils.token'
|
||||
local Queue = require 'utils.queue'
|
||||
|
||||
local random = math.random
|
||||
local queue_new = Queue.new
|
||||
local push = Queue.push
|
||||
local push_to_end = Queue.push_to_end
|
||||
local pop = Queue.pop
|
||||
local peek = Queue.peek
|
||||
local peek_start = Queue.peek_start
|
||||
local peek_index = Queue.peek_index
|
||||
local queue_size = Queue.size
|
||||
local queue_pairs = Queue.pairs
|
||||
|
||||
local snakes = {} -- player_index -> snake_data {is_marked_for_destroy:bool, queue :Queue of {entity, cord} }
|
||||
local board = {
|
||||
size = 0,
|
||||
surface = nil,
|
||||
position = nil,
|
||||
food_count = 0,
|
||||
is_running = false,
|
||||
update_rate = 30,
|
||||
max_food = 6
|
||||
}
|
||||
local cords_map = {} -- cords -> positions
|
||||
|
||||
Global.register(
|
||||
{snakes = snakes, board = board, cords_map = cords_map},
|
||||
function(tbl)
|
||||
snakes = tbl.snakes
|
||||
board = tbl.board
|
||||
cords_map = tbl.cords_map
|
||||
end
|
||||
)
|
||||
|
||||
local vectors = {
|
||||
[0] = {x = 0, y = -1},
|
||||
[1] = {x = 0, y = -1},
|
||||
[2] = {x = 1, y = 0},
|
||||
[3] = {x = 1, y = 0},
|
||||
[4] = {x = 0, y = 1},
|
||||
[5] = {x = 0, y = 1},
|
||||
[6] = {x = -1, y = 0},
|
||||
[7] = {x = -1, y = 0}
|
||||
}
|
||||
|
||||
local function destroy_snake(index, snake)
|
||||
for _, element in queue_pairs(snake.queue) do
|
||||
local e = element.entity
|
||||
if e and e.valid then
|
||||
e.destroy()
|
||||
end
|
||||
end
|
||||
snakes[index] = nil
|
||||
|
||||
local player = game.get_player(index)
|
||||
if not player or not player.valid then
|
||||
return
|
||||
end
|
||||
|
||||
game.print(table.concat {player.name, ' has been destroyed with a score of ', queue_size(snake.queue), '.'})
|
||||
end
|
||||
|
||||
local function destroy_dead_snakes()
|
||||
for index, snake in pairs(snakes) do
|
||||
if snake.is_marked_for_destroy then
|
||||
destroy_snake(index, snake)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function spawn_food()
|
||||
local size = board.size
|
||||
local center = math.ceil(size / 2)
|
||||
local surface = board.surface
|
||||
local find_entity = surface.find_entity
|
||||
|
||||
local food_count = board.food_count
|
||||
local max_food = board.max_food
|
||||
local tries = max_food + 10
|
||||
|
||||
while food_count < max_food and tries > 0 do
|
||||
while tries > 0 do
|
||||
tries = tries - 1
|
||||
local x, y = random(size), random(size)
|
||||
|
||||
if x == center and y == center then
|
||||
goto continue
|
||||
end
|
||||
|
||||
local pos = cords_map[x][y]
|
||||
|
||||
local entity = find_entity('character', pos) or find_entity('compilatron', pos)
|
||||
if entity then
|
||||
goto continue
|
||||
end
|
||||
|
||||
entity =
|
||||
surface.create_entity({name = 'compilatron', position = pos, force = 'neutral', direction = random(7)})
|
||||
entity.active = false
|
||||
food_count = food_count + 1
|
||||
|
||||
do
|
||||
break
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
board.food_count = food_count
|
||||
end
|
||||
|
||||
local function destroy_food()
|
||||
local position = board.position
|
||||
local size = board.size
|
||||
local food =
|
||||
board.surface.find_entities_filtered(
|
||||
{
|
||||
name = 'compilatron',
|
||||
area = {left_top = position, right_bottom = {position.x + size * 2, position.y + size * 2}}
|
||||
}
|
||||
)
|
||||
|
||||
for i = 1, #food do
|
||||
local e = food[i]
|
||||
if e.valid then
|
||||
e.destroy()
|
||||
end
|
||||
end
|
||||
|
||||
board.food_count = 0
|
||||
end
|
||||
|
||||
local function get_new_head_cord(head_cord, direction)
|
||||
local vector = vectors[direction]
|
||||
local vec_x, vec_y = vector.x, vector.y
|
||||
local x, y = head_cord.x + vec_x, head_cord.y + vec_y
|
||||
|
||||
return x, y
|
||||
end
|
||||
|
||||
local function tick_snake(index, snake)
|
||||
local player = game.get_player(index)
|
||||
|
||||
if not player or not player.valid then
|
||||
snake.is_marked_for_destroy = true
|
||||
return
|
||||
end
|
||||
|
||||
local character = player.character
|
||||
if not character or not character.valid then
|
||||
snake.is_marked_for_destroy = true
|
||||
return
|
||||
end
|
||||
|
||||
local surface = board.surface
|
||||
local find_entity = surface.find_entity
|
||||
local snake_queue = snake.queue
|
||||
local snake_size = queue_size(snake_queue)
|
||||
local head = peek_start(snake_queue)
|
||||
local tail = peek(snake_queue)
|
||||
local head_cord = head.cord
|
||||
local tail_entity = tail.entity
|
||||
local tail_cord = tail.cord
|
||||
local size = board.size
|
||||
|
||||
local walking_state = character.walking_state
|
||||
walking_state.walking = true
|
||||
local direction = walking_state.direction
|
||||
local x, y = get_new_head_cord(head_cord, direction)
|
||||
|
||||
if x <= 0 or x > size or y <= 0 or y > size then
|
||||
snake.is_marked_for_destroy = true
|
||||
tail_entity.destroy()
|
||||
return
|
||||
end
|
||||
|
||||
local new_head_position = cords_map[x][y]
|
||||
|
||||
if snake_size > 1 and find_entity('character', new_head_position) == peek_index(snake_queue, snake_size - 1).entity then
|
||||
direction = (direction + 4) % 8
|
||||
walking_state.direction = direction
|
||||
x, y = get_new_head_cord(head_cord, direction)
|
||||
end
|
||||
|
||||
if x <= 0 or x > size or y <= 0 or y > size then
|
||||
snake.is_marked_for_destroy = true
|
||||
tail_entity.destroy()
|
||||
return
|
||||
end
|
||||
|
||||
new_head_position = cords_map[x][y]
|
||||
|
||||
tail_entity.teleport(new_head_position)
|
||||
tail.cord = {x = x, y = y}
|
||||
|
||||
pop(snake_queue)
|
||||
push(snake_queue, tail)
|
||||
|
||||
player.character = nil
|
||||
player.character = tail_entity
|
||||
tail_entity.walking_state = walking_state
|
||||
|
||||
local entity = find_entity('compilatron', new_head_position)
|
||||
if entity and entity.valid then
|
||||
entity.destroy()
|
||||
|
||||
entity =
|
||||
surface.create_entity {name = 'character', position = cords_map[tail_cord.x][tail_cord.y], force = 'player'}
|
||||
entity.character_running_speed_modifier = -1
|
||||
push_to_end(snake_queue, {entity = entity, cord = tail_cord})
|
||||
|
||||
board.food_count = board.food_count - 1
|
||||
end
|
||||
end
|
||||
|
||||
local function tick_snakes()
|
||||
for index, snake in pairs(snakes) do
|
||||
tick_snake(index, snake)
|
||||
end
|
||||
end
|
||||
|
||||
local function check_snakes_for_collisions()
|
||||
local count_entities_filtered = board.surface.count_entities_filtered
|
||||
for index, snake in pairs(snakes) do
|
||||
if snake.is_marked_for_destroy then
|
||||
goto continue
|
||||
end
|
||||
|
||||
if count_entities_filtered({name = 'character', position = peek_start(snake.queue).entity.position}) > 1 then
|
||||
snake.is_marked_for_destroy = true
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
local tick
|
||||
tick =
|
||||
Token.register(
|
||||
function()
|
||||
if not board.is_running then
|
||||
return
|
||||
end
|
||||
|
||||
tick_snakes()
|
||||
check_snakes_for_collisions()
|
||||
destroy_dead_snakes()
|
||||
spawn_food()
|
||||
|
||||
Task.set_timeout_in_ticks(board.update_rate, tick)
|
||||
end
|
||||
)
|
||||
|
||||
local function make_board()
|
||||
local size = board.size
|
||||
local position = board.position
|
||||
local surface = board.surface
|
||||
|
||||
local pos_x, pos_y = position.x, position.y
|
||||
|
||||
for x = 1, size do
|
||||
local col = {}
|
||||
cords_map[x] = col
|
||||
for y = 1, size do
|
||||
col[y] = {pos_x + 2 * x - 0.5, pos_y + 2 * y - 0.5}
|
||||
end
|
||||
end
|
||||
|
||||
size = size * 2
|
||||
local tiles = {}
|
||||
|
||||
for x = 0, size do
|
||||
for y = 0, size do
|
||||
local pos = {pos_x + x, pos_y + y}
|
||||
local tile_name
|
||||
|
||||
if x == 0 or x == size or y == 0 or y == size then
|
||||
tile_name = 'deepwater'
|
||||
elseif x % 2 == 1 and y % 2 == 1 then
|
||||
tile_name = 'grass-1'
|
||||
else
|
||||
tile_name = 'water'
|
||||
end
|
||||
|
||||
tiles[#tiles + 1] = {position = pos, name = tile_name}
|
||||
end
|
||||
end
|
||||
|
||||
surface.set_tiles(tiles)
|
||||
end
|
||||
|
||||
local function new_snake(player)
|
||||
if not board.is_running then
|
||||
return
|
||||
end
|
||||
|
||||
if not player or not player.valid then
|
||||
return
|
||||
end
|
||||
|
||||
if snakes[player.index] then
|
||||
return
|
||||
end
|
||||
|
||||
local character = player.character
|
||||
if character and character.valid then
|
||||
character.destroy()
|
||||
end
|
||||
|
||||
local size = board.size
|
||||
local center = math.ceil(size / 2)
|
||||
|
||||
player.teleport(cords_map[center][center])
|
||||
|
||||
player.create_character()
|
||||
character = player.character
|
||||
character.character_running_speed_modifier = -1
|
||||
|
||||
local queue = queue_new()
|
||||
push(queue, {entity = character, cord = {x = center, y = center}})
|
||||
local snake = {queue = queue}
|
||||
|
||||
snakes[player.index] = snake
|
||||
end
|
||||
|
||||
local function new_game(surface, position, size, update_rate, max_food)
|
||||
board.size = size
|
||||
board.surface = surface
|
||||
position.x = position.x
|
||||
position.y = position.y
|
||||
board.position = position
|
||||
board.update_rate = update_rate
|
||||
board.max_food = max_food
|
||||
|
||||
make_board()
|
||||
destroy_food()
|
||||
spawn_food()
|
||||
|
||||
board.is_running = true
|
||||
|
||||
Task.set_timeout_in_ticks(board.update_rate, tick)
|
||||
end
|
||||
|
||||
local Public = {}
|
||||
|
||||
function Public.start_game(surface, top_left_position, size, update_rate, max_food)
|
||||
if board.is_running then
|
||||
error('Snake game is already running you must end the game first.', 2)
|
||||
end
|
||||
|
||||
new_game(surface, top_left_position, size, update_rate or board.update_rate, max_food or board.max_food)
|
||||
end
|
||||
|
||||
function Public.end_game()
|
||||
for i = 1, #snakes do
|
||||
destroy_snake(i, snakes[i])
|
||||
end
|
||||
|
||||
destroy_food()
|
||||
|
||||
board.is_running = false
|
||||
end
|
||||
|
||||
function Public.new_snake(player)
|
||||
new_snake(player)
|
||||
end
|
||||
|
||||
function Public.is_running()
|
||||
return board.is_running
|
||||
end
|
||||
|
||||
return Public
|
51
features/snake/gui.lua
Normal file
51
features/snake/gui.lua
Normal file
@ -0,0 +1,51 @@
|
||||
local Game = require 'features.snake.game'
|
||||
local Gui = require 'utils.gui'
|
||||
local Event = require 'utils.event'
|
||||
|
||||
local Public = {}
|
||||
|
||||
local main_button_name = Gui.uid_name()
|
||||
|
||||
local function show_gui_for_player(player)
|
||||
if not player or not player.valid then
|
||||
return
|
||||
end
|
||||
|
||||
local top = player.gui.top
|
||||
if not top[main_button_name] then
|
||||
top.add {type = 'button', name = main_button_name, caption = 'Snake'}
|
||||
end
|
||||
end
|
||||
|
||||
local function player_created(event)
|
||||
if Game.is_running() then
|
||||
local player = game.get_player(event.player_index)
|
||||
show_gui_for_player(player)
|
||||
end
|
||||
end
|
||||
|
||||
Event.add(defines.events.on_player_created, player_created)
|
||||
|
||||
function Public.show()
|
||||
for _, player in pairs(game.players) do
|
||||
show_gui_for_player(player)
|
||||
end
|
||||
end
|
||||
|
||||
function Public.destroy()
|
||||
for _, player in pairs(game.players) do
|
||||
local button = player.gui.top[main_button_name]
|
||||
if button and button.valid then
|
||||
button.destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gui.on_click(
|
||||
main_button_name,
|
||||
function(event)
|
||||
Game.new_snake(event.player)
|
||||
end
|
||||
)
|
||||
|
||||
return Public
|
Loading…
x
Reference in New Issue
Block a user