@rbxts/htn-shop 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/out/index.d.ts +2 -0
- package/out/init.luau +87 -0
- package/out/types.d.ts +17 -0
- package/out/types.luau +8 -0
- package/out/util.d.ts +3 -0
- package/out/util.luau +31 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @rbxts/shop
|
|
2
|
+
|
|
3
|
+
a simple HTN, SHOP-like planner written in js, based on [Pyhop](https://bitbucket.org/dananau/pyhop)
|
|
4
|
+
|
|
5
|
+
shop was easy to implement (less than 50 lines of code), and if you understand the basic ideas of HTN planning ([this presentation](http://www.cs.umd.edu/~nau/papers/nau2013game.pdf) contains a quick summary), shop should be easy to understand.
|
|
6
|
+
|
|
7
|
+
shop's planning algorithm is like the one in SHOP, but with several differences that should make it easier to integrate it with ordinary computer programs:
|
|
8
|
+
|
|
9
|
+
shop represents states of the world using ordinary variable bindings, not logical propositions. A state is just a js object. For example, you might write s.loc['v'] = 'd' to say that vehicle v is at location d in state s.
|
|
10
|
+
|
|
11
|
+
To write HTN operators and methods forjshop, you don't need to learn a specialized planning language. Instead, you write them as ordinary js functions. The current state (e.g., s in the above example) is passed to them as an argument.
|
|
12
|
+
|
|
13
|
+
## Module Usage
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
npm install @rbxts/shop
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
see the unit.test.ts file for an example usage. more to follow.
|
|
20
|
+
|
|
21
|
+
## Further reading
|
|
22
|
+
|
|
23
|
+
- Designing games mechanics with a HTN Planner - [PDF](http://www.gameaipro.com/GameAIPro/GameAIPro_Chapter12_Exploring_HTN_Planners_through_Example.pdf)
|
|
24
|
+
|
|
25
|
+
- Use in video games - [Youtube](https://youtu.be/kXm467TFTcY)
|
|
26
|
+
|
|
27
|
+
## License
|
|
28
|
+
|
|
29
|
+
MIT
|
package/out/index.d.ts
ADDED
package/out/init.luau
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = require(game:GetService("ReplicatedStorage"):WaitForChild("rbxts_include"):WaitForChild("RuntimeLib"))
|
|
3
|
+
local _util = TS.import(script, game:GetService("ReplicatedStorage"), "shop", "util")
|
|
4
|
+
local extend = _util.extend
|
|
5
|
+
local deepClone = _util.deepClone
|
|
6
|
+
local tail = _util.tail
|
|
7
|
+
local addOperators, setMethods, solve
|
|
8
|
+
local function create()
|
|
9
|
+
local operators = {}
|
|
10
|
+
local taskMethods = {}
|
|
11
|
+
return {
|
|
12
|
+
operators = function(toAdd)
|
|
13
|
+
return addOperators(operators, toAdd)
|
|
14
|
+
end,
|
|
15
|
+
setMethods = function(taskName, methods)
|
|
16
|
+
return setMethods(taskMethods, taskName, methods)
|
|
17
|
+
end,
|
|
18
|
+
solve = function(state, tasks)
|
|
19
|
+
return solve(operators, taskMethods, state, tasks)
|
|
20
|
+
end,
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
function addOperators(currentOperators, toAdd)
|
|
24
|
+
extend(currentOperators, toAdd)
|
|
25
|
+
end
|
|
26
|
+
function setMethods(currentTaskMethods, taskName, toAdd)
|
|
27
|
+
currentTaskMethods[taskName] = toAdd
|
|
28
|
+
end
|
|
29
|
+
local seekPlan
|
|
30
|
+
function solve(operators, taskMethods, state, tasks)
|
|
31
|
+
return seekPlan(operators, taskMethods, state, tasks, {}, 0)
|
|
32
|
+
end
|
|
33
|
+
function seekPlan(operators, taskMethods, state, tasks, plan, depth)
|
|
34
|
+
if #tasks == 0 then
|
|
35
|
+
return plan
|
|
36
|
+
end
|
|
37
|
+
local task1 = tasks[1]
|
|
38
|
+
local taskName = task1[1]
|
|
39
|
+
if operators[taskName] ~= nil then
|
|
40
|
+
local operator = operators[taskName]
|
|
41
|
+
local args = tail(task1)
|
|
42
|
+
local newstate = operator(deepClone(state), unpack(args))
|
|
43
|
+
if newstate then
|
|
44
|
+
local _exp = operators
|
|
45
|
+
local _exp_1 = taskMethods
|
|
46
|
+
local _exp_2 = tail(tasks)
|
|
47
|
+
local _array = {}
|
|
48
|
+
local _length = #_array
|
|
49
|
+
local _planLength = #plan
|
|
50
|
+
table.move(plan, 1, _planLength, _length + 1, _array)
|
|
51
|
+
_length += _planLength
|
|
52
|
+
_array[_length + 1] = task1
|
|
53
|
+
local solution = seekPlan(_exp, _exp_1, newstate, _exp_2, _array, depth + 1)
|
|
54
|
+
if solution ~= nil then
|
|
55
|
+
return solution
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
if taskMethods[taskName] ~= nil then
|
|
60
|
+
local solution = nil
|
|
61
|
+
for _, method in taskMethods[taskName] do
|
|
62
|
+
local args = tail(task1)
|
|
63
|
+
local subtasks = method(state, unpack(args))
|
|
64
|
+
if subtasks ~= nil then
|
|
65
|
+
local _exp = operators
|
|
66
|
+
local _exp_1 = taskMethods
|
|
67
|
+
local _exp_2 = state
|
|
68
|
+
local _array = {}
|
|
69
|
+
local _length = #_array
|
|
70
|
+
local _subtasksLength = #subtasks
|
|
71
|
+
table.move(subtasks, 1, _subtasksLength, _length + 1, _array)
|
|
72
|
+
_length += _subtasksLength
|
|
73
|
+
local _array_1 = tail(tasks)
|
|
74
|
+
table.move(_array_1, 1, #_array_1, _length + 1, _array)
|
|
75
|
+
solution = seekPlan(_exp, _exp_1, _exp_2, _array, plan, depth + 1)
|
|
76
|
+
if solution ~= nil then
|
|
77
|
+
break
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
return solution
|
|
82
|
+
end
|
|
83
|
+
return nil
|
|
84
|
+
end
|
|
85
|
+
return {
|
|
86
|
+
create = create,
|
|
87
|
+
}
|
package/out/types.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** A task is represented as an array where the first element is the task name and the rest are arguments */
|
|
2
|
+
export type Task = [string, ...defined[]];
|
|
3
|
+
/** State can be any object with string keys */
|
|
4
|
+
export type State = Record<string, defined>;
|
|
5
|
+
/** An operator takes a state and task arguments, returns a new state or undefined if not applicable */
|
|
6
|
+
export type Operator<S extends State> = (state: S, ...args: defined[]) => S | undefined;
|
|
7
|
+
/** A method takes a state and task arguments, returns subtasks or undefined if not applicable */
|
|
8
|
+
export type Method<S extends State> = (state: S, ...args: defined[]) => Task[] | undefined;
|
|
9
|
+
/** Collection of operators keyed by task name */
|
|
10
|
+
export type Operators<S extends State> = Record<string, Operator<S>>;
|
|
11
|
+
/** Collection of methods for a task (multiple methods can apply to the same task) */
|
|
12
|
+
export type TaskMethods<S extends State> = Record<string, Method<S>[]>;
|
|
13
|
+
export interface Planner<S extends State> {
|
|
14
|
+
operators: (toAdd: Operators<S>) => void;
|
|
15
|
+
setMethods: (taskName: string, methods: Method<S>[]) => void;
|
|
16
|
+
solve: (state: S, tasks: Task[]) => Task[] | undefined;
|
|
17
|
+
}
|
package/out/types.luau
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
--* A task is represented as an array where the first element is the task name and the rest are arguments
|
|
3
|
+
--* State can be any object with string keys
|
|
4
|
+
--* An operator takes a state and task arguments, returns a new state or undefined if not applicable
|
|
5
|
+
--* A method takes a state and task arguments, returns subtasks or undefined if not applicable
|
|
6
|
+
--* Collection of operators keyed by task name
|
|
7
|
+
--* Collection of methods for a task (multiple methods can apply to the same task)
|
|
8
|
+
return nil
|
package/out/util.d.ts
ADDED
package/out/util.luau
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local function deepClone(obj)
|
|
3
|
+
local _obj = obj
|
|
4
|
+
if typeof(_obj) ~= "table" then
|
|
5
|
+
return obj
|
|
6
|
+
end
|
|
7
|
+
local result = {}
|
|
8
|
+
for key, value in pairs(obj) do
|
|
9
|
+
result[key] = deepClone(value)
|
|
10
|
+
end
|
|
11
|
+
return result
|
|
12
|
+
end
|
|
13
|
+
local function tail(arr)
|
|
14
|
+
local result = {}
|
|
15
|
+
for i = 1, #arr - 1 do
|
|
16
|
+
local _arg0 = arr[i + 1]
|
|
17
|
+
table.insert(result, _arg0)
|
|
18
|
+
end
|
|
19
|
+
return result
|
|
20
|
+
end
|
|
21
|
+
local function extend(target, source)
|
|
22
|
+
for key, value in pairs(source) do
|
|
23
|
+
target[key] = value
|
|
24
|
+
end
|
|
25
|
+
return target
|
|
26
|
+
end
|
|
27
|
+
return {
|
|
28
|
+
deepClone = deepClone,
|
|
29
|
+
tail = tail,
|
|
30
|
+
extend = extend,
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rbxts/htn-shop",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "a simple HTN SHOP-like planner",
|
|
5
|
+
"author": "shrjrd",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"planning",
|
|
9
|
+
"planner",
|
|
10
|
+
"shop",
|
|
11
|
+
"HTN"
|
|
12
|
+
],
|
|
13
|
+
"main": "out/init.lua",
|
|
14
|
+
"types": "out/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"out",
|
|
17
|
+
"!**/*.tsbuildinfo",
|
|
18
|
+
"!out/*.test.*",
|
|
19
|
+
"!out/jest.*"
|
|
20
|
+
],
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
23
|
+
"@rbxts/jest": "^3.13.3-alpha.2",
|
|
24
|
+
"@rbxts/jest-globals": "^3.13.3-alpha.2",
|
|
25
|
+
"@rbxts/types": "^1.0.894",
|
|
26
|
+
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
|
27
|
+
"@typescript-eslint/parser": "^8.48.0",
|
|
28
|
+
"eslint": "^9.39.1",
|
|
29
|
+
"eslint-config-prettier": "^10.1.8",
|
|
30
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
31
|
+
"eslint-plugin-roblox-ts": "^1.3.0",
|
|
32
|
+
"prettier": "^3.7.0",
|
|
33
|
+
"roblox-ts": "^3.0.0",
|
|
34
|
+
"rojo": "^1.0.3-rojo7.6.1",
|
|
35
|
+
"typescript": "^5.9.3"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "rbxtsc",
|
|
39
|
+
"build:dev": "rbxtsc --type game --rojo default.project.json",
|
|
40
|
+
"build:rbxl": "rojo sourcemap -o sourcemap.json && rojo build -o place.rbxl",
|
|
41
|
+
"watch": "npm run build:dev && npx concurrently \"npm run build:dev -- -w\" \"rojo serve\""
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/shrjrd/rbxts-htn-shop.git"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
}
|
|
50
|
+
}
|