PHP REST API text-based game engine
API testing endpoint:
https://text.n0p.cz/
The easiest way to deploy this app is using Docker. This repo is automatically synced with Docker Cloud Build and the stable image is put on https://hub.docker.com/r/krustowski/textovka-api.
To run a container, just type following into you server running Docker (app is ported locally to :8080
):
# the image is pulled automatically by docker
docker run -d -p 8080:80 --name textovka-api krustowski/textovka-api
The API endpoint is then located on http://localhost:8080.
git clone https://github.com/krustowski/textovka-api.git ~/textovka-api/
docker build ~/textovka-api --tag=textovka-api-build
docker run -d -p 8080:80 --name textovka-api textovka-api-build
To run this API properly, you need PHP 7.3+ with php-fpm
(PHP processor for proxy, voluntarily) and php-json
(JSON processor) packages installed. Then I recommend using nginx
as proxy, as it is very easy to set up.
A raw step-by-step solution can be observed in Dockerfile.
index.php
An index file executed while endpoint is opened.
src/Game.php
The engine file.
prod.sh
A bash script used for a rapid update of the repo on the production server (frank
) –
a whole data structure there is removed and newely untared (data/
directory is flushed).
maps/demo.json
A JSON file with the server-side map. The required format is described in detail below. Otherwise, an API exception could be raised.
game.log
A file containing the server-side log with the following format:
timestamp / nickname / action / IP
data/
A directory where players' data are stored as JSON files.
For valid HTTPS communication cURL
tool can be used. The registration is performed via GET request:
ENDPOINT="https://text.n0p.cz"
curl -sSL "$ENDPOINT/?register=user_name"
API key (apikey
) can be extracted using jq
tool:
APIKEY=$(curl -sSL "$ENDPOINT/?register=user_name" | jq '.api.apikey')
New user is "registred" as a new JSON file data/$HASH.json
. The map is assigned randomly from maps/
directory. For the further playing, the apikey
has to be included in the query string:
curl -sSL "$ENDPOINT/?apikey=$APIKEY&action=go-north"
variable | desc | range | default |
---|---|---|---|
nickname | user nickname | 32 chars max | |
hp | health level | 0-100 | 100 |
inventary | JSON array of picked items | JSON array | [] |
room | current room | start_room defined in map |
|
time_registred | self-explanatory | current UNIX timestamp | |
time_ended | self-explanatory | game ended UNIX timestamp | |
game_ended | self-explanatory | boolean | false |
map | randomly assigned map | maps/demo.json |
User JSON structure example (map
part trimmed):
{
"nickname": "krusty",
"hp": 86,
"inventary": [
"magic-key"
],
"room": "gg",
"time_registred": 1586542166,
"time_ended": 1586542238,
"game_ended": true,
"map": {
}
}
Maps are stored in maps/
directory. The default one is demo.json
and is very simple. Each map has rooms. These are stored in the room
object with their proper names (0001
, well
, aa
, ly
etc). Each room should have its description
- shown as message to the user when entered. Furthermore, directions (north
, south
, east
, west
) should be defined as other room names; but it is not neccessary (trap rooms). Objects and items are listed in arrays objects
and items
. Actions can be enumerated in actions
array. If so, effects
array HAS to be filled properly too, otherwise the API will throw an exception (Invalid map). Each room-defined action can execute an hidden effects (description
is overwitten, new direction
is unlocked etc).
variable | purpose | required |
---|---|---|
name | pointer for other rooms | yes |
description | used as message for user when entered | no, but strongly recommended |
directions | defines further paths from such room | no (in case of the trap room) |
items | list of items (for pick for example) | no if not defined in effects as required-item |
objects | list of room objects (water, fire etc) | no if not required in effects as object |
actions | list of room defined actions | no (transit room, blank room) |
effects | specifications of room actions | yes if actions are listed! (otherwise results in Invalid map exception) |
hidden | defines room variables to be overwriten by an action | no if not defined in effects as show-hidden |
Simple map example (only one room):
{
"room": {
"0001": {
"description": "A very dark room",
"north": "0010",
"south": "0100",
"west": "0011",
"items": [
"bucket"
],
"objects": [
"water"
],
"actions": [
"pick-water"
],
"effects": {
"pick-water": {
"type": "fill",
"required-item": "bucket",
"object": "water",
"show-hidden": true,
"damage-hp": [
0, 5
]
}
},
"hidden": {
"description": "The very same room, but the water level is lower now.",
"east": "0101"
}
}
}
}
The main game log in stored in root directory as game.log
.
variable | purpose |
---|---|
timestamp | UNIX timestamp (API response timestamp) |
name | user nickname |
action | action sent by the user to API |
IP | IP address of the user |
Trimmed example of the log:
1586542166 / krusty / register / IP
1586542166 / krusty / none / IP
1586542169 / krusty / go-south / IP
1586542170 / krusty / pick-bucket / IP
1586542171 / krusty / go-north / IP
1586542172 / krusty / go-north / IP
1586542173 / krusty / pick-water / IP
1586542174 / krusty / go-south / IP
1586542175 / krusty / go-east / IP
1586542177 / krusty / quench-fire / IP
1586542200 / krusty / go-east / IP
By default, each room has four actions (directions): go-north
, go-south
, go-east
, go-west
. It is on the map designer, whether these are going to be
implemented in such room.
Room-defined action types:
type | desc |
---|---|
pick | used for picking items in room (those have to be listed in items ) |
dismiss | self-explanatory, used for fire quenching for example |
fill | used to fill other objects in invetary (bucket with water etc) |
fight | to be implemented in v2 |
* | generic action (door unlocking etc) |
Python3 npyscreen TUI:
https://github.com/krustowski/textovka-tui
- fights with NPCs (used item defines its damage)
- basic multiplayer support (shared maps, paired map solving, fighting etc)
- map choosing @ registration (list given before registration, optional argument in GET request)
- player's password (hashed in JSON, prolly sha512)
- locking oneself at their house (flats/blocks in the map)
- possibility of killing others inside rooms/blocks/flats (how the defend oneself?)
- API returns other room names @ its directions -> can be projected directly to TUI
- queue of requests, or map locks (shared map is being overwritten, or compare/diff players' maps)
dockerfile + nginx simple config (simple install)- return
"room_visited": true/false
inroom
array - assign
engine_build
to player @ registration -> 'new API version' notification - map API logs to docker logs