Skip to content
Snippets Groups Projects
Commit adf6a581 authored by dg's avatar dg
Browse files

A* algorithm

Key.status now knows if a key is up or down at any given time


git-svn-id: http://svn.net-core.org/repos/t-engine4@589 51575b47-30f0-44d4-a5cc-537603b46e54
parent d920922f
No related branches found
No related tags found
No related merge requests found
-- TE4 - T-Engine 4
-- Copyright (C) 2009, 2010 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
require "engine.class"
--- Pathfinding using A*
module(..., package.seeall, class.make)
function _M:init(map, actor)
self.map = map
self.actor = actor
end
--- The default heuristic for A*, simple distance
function _M:heuristic(sx, sy, tx, ty)
return core.fov.distance(sx, sy, tx, ty)
end
function _M:toSingle(x, y)
return x + y * self.map.w
end
function _M:toDouble(c)
local y = math.floor(c / self.map.w)
return c - y * self.map.w, y
end
function _M:createPath(came_from, cur)
if not came_from[cur] then return end
local path = {}
while came_from[cur] do
local x, y = self:toDouble(cur)
path[#path+1] = {x=x,y=y}
cur = came_from[cur]
end
return path
end
--- Compute path from sx/sy to tx/ty
-- @param sx the start coord
-- @param sy the start coord
-- @param tx the end coord
-- @param ty the end coord
-- @return either nil if no path or a list of nodes in the form { {x=...,y=...}, {x=...,y=...}, ..., {x=tx,y=ty}}
function _M:calc(sx, sy, tx, ty)
local w, h = self.map.w, self.map.h
local start = self:toSingle(sx, sy)
local stop = self:toSingle(tx, ty)
local open = {[start]=true}
local closed = {}
local g_score = {[start] = 0}
local h_score = {[start] = self:heuristic(sx, sy, tx, ty)}
local f_score = {[start] = self:heuristic(sx, sy, tx, ty)}
local came_from = {}
local checkPos = function(node, nx, ny)
local nnode = self:toSingle(nx, ny)
if not closed[nnode] and self.map:isBound(nx, ny) and not self.map:checkAllEntities(nx, ny, "block_move", self.actor) then
local tent_g_score = g_score[node] + 1 -- we can adjust hre for difficult passable terrain
local tent_is_better = false
if not open[nnode] then open[nnode] = true; tent_is_better = true
elseif tent_g_score < g_score[nnode] then tent_is_better = true
end
if tent_is_better then
came_from[nnode] = node
g_score[nnode] = tent_g_score
h_score[nnode] = self:heuristic(tx, ty, nx, ny)
f_score[nnode] = g_score[nnode] + h_score[nnode]
end
end
end
while next(open) do
-- Find lowest of f_score
local node, lowest = nil, 999999999999999
for n, _ in pairs(open) do
if f_score[n] < lowest then node = n; lowest = f_score[n] end
end
if node == stop then return self:createPath(came_from, stop) end
open[node] = nil
closed[node] = true
local x, y = self:toDouble(node)
-- Check right
checkPos(node, x + 1, y)
checkPos(node, x - 1, y)
checkPos(node, x, y + 1)
checkPos(node, x, y - 1)
end
end
......@@ -24,6 +24,7 @@ require "engine.class"
module(..., package.seeall, class.make)
function _M:init()
self.status = {}
end
--- Called when a key is pressed
......@@ -33,8 +34,15 @@ end
-- @param alt is the alt key pressed?
-- @param meta is the meta key pressed?
-- @param unicode the unicode representation of the key, if possible
function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode)
print(sym, ctrl, shift, alt, meta, unicode)
-- @param isup true if the key was released, false if pressed
function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode, isup)
print(sym, ctrl, shift, alt, meta, unicode, isup)
self:handleStatus(sym, ctrl, shift, alt, meta, unicode, isup)
end
--- Maintain the self.status table, which can be used to know if a key is currently pressed
function _M:handleStatus(sym, ctrl, shift, alt, meta, unicode, isup)
self.status[sym] = not isup
end
--- Setups as the current game keyhandler
......
......@@ -164,7 +164,11 @@ function _M:formatKeyString(ks)
end
end
function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode)
function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode, isup)
self:handleStatus(sym, ctrl, shift, alt, meta, unicode, isup)
if isup then return end
local ks, us = self:makeKeyString(sym, ctrl, shift, alt, meta, unicode)
-- print("[BIND]", sym, ctrl, shift, alt, meta, unicode, " :=: ", ks, us, " ?=? ", self.binds[ks], us and self.binds[us])
if self.binds[ks] and self.virtuals[self.binds[ks]] then
......@@ -175,7 +179,7 @@ function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode)
return
end
engine.KeyCommand.receiveKey(self, sym, ctrl, shift, alt, meta, unicode)
engine.KeyCommand.receiveKey(self, sym, ctrl, shift, alt, meta, unicode, isup)
end
--- Adds a key/command combinaison
......
......@@ -49,7 +49,11 @@ function _M:setupProfiler()
end)
end
function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode)
function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode, isup)
self:handleStatus(sym, ctrl, shift, alt, meta, unicode, isup)
if isup then return end
-- Convert locale
sym = self.locale_convert[sym] or sym
......
......@@ -31,7 +31,10 @@ end
-- @param button
-- @param x coordinate of the click
-- @param y coordinate of the click
function _M:receiveMouse(button, x, y)
-- @param isup true if the key was released, false if pressed
function _M:receiveMouse(button, x, y, isup)
if not isup then return end
for i, m in ipairs(self.areas) do
if (not m.mode or m.mode.button) and (x >= m.x1 and x < m.x2 and y >= m.y1 and y < m.y2) then
m.fct(button, x, y, nil, nil, x-m.x1, y-m.y1)
......
......@@ -328,6 +328,13 @@ function _M:display()
self.target.target.x, self.target.target.y = tmx, tmy
end
self.old_tmx, self.old_tmy = tmx, tmy
if self.test_path and self.key.status[self.key._LSHIFT] then
for i, c in ipairs(self.test_path) do
local lx, ly = c.x, c.y
self.test_sprite:toScreen(self.level.map.display_x + (lx - game.level.map.mx) * self.level.map.tile_w, self.level.map.display_y + (ly - game.level.map.my) * self.level.map.tile_h)
end
end
end
engine.GameTurnBased.display(self)
......@@ -655,20 +662,17 @@ function _M:setupMouse()
self.mouse:registerZone(Map.display_x, Map.display_y, Map.viewport.width, Map.viewport.height, function(button, mx, my, xrel, yrel)
-- Move tooltip
self.tooltip_x, self.tooltip_y = mx, my
local tmx, tmy = self.level.map:getMouseTile(mx, my)
-- Target stuff
if button == "right" then
local tmx, tmy = self.level.map:getMouseTile(mx, my)
-- DEBUG
-- if config.settings.tome.cheat then
-- game.player:move(tmx, tmy, true)
-- else
local Astar = require"engine.Astar"
local a = Astar.new(self.level.map, self.player)
local path = a:CalcPath(a:CalcMoves(self.player.x, self.player.y, tmx, tmy))
print("A* from", self.player.x, self.player.y, "to", tmx, tmy)
for i, c in ipairs(path) do print("A*", c.x, c.y) end
-- end
if config.settings.tome.cheat then
game.player:move(tmx, tmy, true)
else
-- Move along the projected A* path
end
-- Move map around
elseif button == "left" and xrel and yrel then
derivx = derivx + xrel
......@@ -689,6 +693,18 @@ function _M:setupMouse()
derivy = derivy + game.level.map.tile_h
end
game.level.map._map:setScroll(game.level.map.mx, game.level.map.my)
elseif button == "none" then
if self.key.status[self.key._LSHIFT] and self.test_x ~= tmx or self.test_y ~= tmy then
local Astar = require"engine.Astar"
local a = Astar.new(self.level.map, self.player)
local path = a:calc(self.player.x, self.player.y, tmx, tmy)
self.test_x = tmx
self.text_y = tmy
self.test_path = path
self.test_sprite = core.display.newSurface(self.level.map.tile_w, self.level.map.tile_h)
self.test_sprite:erase(0, 0, 255, 90)
end
end
end)
-- Scroll message log
......
......@@ -108,6 +108,7 @@ void on_event(SDL_Event *event)
{
switch (event->type) {
case SDL_KEYDOWN:
case SDL_KEYUP:
if (current_keyhandler != LUA_NOREF)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, current_keyhandler);
......@@ -146,9 +147,11 @@ void on_event(SDL_Event *event)
}
else
lua_pushnil(L);
docall(L, 7, 0);
lua_pushboolean(L, (event->type == SDL_KEYUP) ? TRUE : FALSE);
docall(L, 8, 0);
}
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
if (current_mousehandler != LUA_NOREF)
{
......@@ -177,7 +180,8 @@ void on_event(SDL_Event *event)
}
lua_pushnumber(L, event->button.x);
lua_pushnumber(L, event->button.y);
docall(L, 4, 0);
lua_pushboolean(L, (event->type == SDL_MOUSEBUTTONUP) ? TRUE : FALSE);
docall(L, 5, 0);
}
break;
case SDL_MOUSEMOTION:
......@@ -542,8 +546,10 @@ int main(int argc, char *argv[])
break;
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
case SDL_KEYDOWN:
case SDL_KEYUP:
/* handle key presses */
on_event(&event);
break;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment