<?php
// example using the NonBlockingCLI class
// RPG MUD-like dungeon adventure game example with NPCs and Entities
include_once('class.nb_cli_1.0.php');
// BEGIN :: AREA DEFINITION
$rooms_arr = array(
"room1" => array(
"name" => "Room 1",
"desc" => "This is room 1",
"exits" => array(
"north" => "room2",
"south" => "room3",
"east" => "room4",
"west" => "room5"
),
"starting_room" => true // our player starting room
),
"room2" => array(
"name" => "Room 2",
"desc" => "This is room 2",
"exits" => array(
"south" => "room1"
)
),
"room3" => array(
"name" => "Room 3",
"desc" => "This is room 3",
"exits" => array(
"north" => "room1"
)
),
"room4" => array(
"name" => "Room 4",
"desc" => "This is room 4",
"exits" => array(
"west" => "room1"
)
),
"room5" => array(
"name" => "Room 5",
"desc" => "This is room 5",
"exits" => array(
"east" => "room1"
)
)
);
class c_room {
public $uid;
public $data = [];
public function __construct() {
$this->uid = uniqid();
}
public function set($key, $value) {
$this->data[$key] = $value;
}
public function get($key) {
if (isset($this->data[$key]) )
return $this->data[$key];
}
}
class c_rooms {
private $rooms = array();
public function __construct() {
}
public function get_start()
{
foreach($this->rooms as $room_uid => $room) {
if($room->get('starting_room') == true) {
$starting_rooms[] = $room;
}
}
if(count($starting_rooms) == 0) {
return false;
}
// return a random starting room
return $starting_rooms[array_rand($starting_rooms)]->uid;
}
// get random room
public function get_random()
{
return $this->rooms[array_rand($this->rooms)];
}
public function show() {
// loop through each one and print_r it
foreach($this->rooms as $room_uid => $room) {
print_r($room);
}
}
public function get_exits($uid)
{
if(isset($this->rooms[$uid]) == false)
return false;
return $this->rooms[$uid]->get('exits_uid');
}
public function add() {
$r = new c_room();
$this->rooms[$r->uid] = $r;
return $this->rooms[$r->uid];
}
public function get($uid) {
if(isset($this->rooms[$uid]) == false) {
return false;
}
return $this->rooms[$uid];
}
public function remove($uid) {
if(isset($this->rooms[$uid]) == false) {
return false;
}
unset($this->rooms[$uid]);
return true;
}
// add from array
public function add_from_array($rooms_arr) {
// use array $rooms_arr as reference
foreach($rooms_arr as $read_id => $room_data) {
$r = $this->add();
$r->set('read_id', $read_id);
foreach($room_data as $key => $value) {
$r->set($key, $value);
}
}
// now go through and link up exits and add a exits_uid array to rooms with exits
foreach($this->rooms as $room_uid => $room) {
if(isset($room->data['exits'])) {
$exits_uid = [];
foreach($room->data['exits'] as $exit_direction => $read_id) {
// $exits_uid[$exit_direction] = $this->get($exit_room_uid)->uid;
// find room that matches read_id
foreach($this->rooms as $room_uid2 => $room2) {
if($room2->get('read_id') == $read_id) {
$exits_uid[$exit_direction] = $room2->uid;
}
}
}
$room->set('exits_uid', $exits_uid);
}
}
}
}
class c_entity {
public $uid;
public $data = [];
public $c_entities;
private $stdin = NULL;
private $stdout = NULL;
public function __construct($c_entities) {
$this->uid = uniqid();
$this->set("cur room", -1);
$this->c_entities = $c_entities;
}
public function set($key, $value) {
$this->data[$key] = $value;
}
public function get($key) {
if (isset($this->data[$key]) )
return $this->data[$key];
}
public function set_local()
{
$this->set("local", true);
// set non-blocking mode for STDIN
$this->stdin = fopen('php://stdin', 'r');
$this->stdout = fopen('php://stdout', 'w');
}
public function out($msg) {
if($this->get("local") == true)
fwrite($this->stdout, $msg);
}
public function do_prompt()
{
$this->out("\r\n(Type 'help' or '?' for help) > ");
}
}
class c_entities {
public $entities = array();
public function __construct() {
}
public function add() {
$e = new c_entity($this);
$this->entities[$e->uid] = $e;
return $this->entities[$e->uid];
}
public function get($uid) {
if(isset($this->entities[$uid]) == false) {
return false;
}
return $this->entities[$uid];
}
public function get_all_in_room($room_uid)
{
$entities_in_room = [];
foreach($this->entities as $entity_uid => $entity)
if($entity->get("cur room") == $room_uid)
$entities_in_room[] = $entity;
return $entities_in_room;
}
public function get_others_in_room($room_uid, $src_entity_uid)
{
$entities_in_room = $this->get_all_in_room($room_uid);
// remove if uid matches src_entity_uid
foreach($entities_in_room as $entity_uid => $entity)
if($entity->uid == $src_entity_uid)
unset($entities_in_room[$entity_uid]);
return $entities_in_room;
}
public function remove($uid) {
if(isset($this->entities[$uid]) == false) {
return false;
}
unset($this->entities[$uid]);
return true;
}
}
### BEGIN: non-blocking cli class related ###
class my_plugin extends nb_plugin {
private $stdin; private $stdout;
public $data = [];
// helper classes
public $c_rooms;
public $c_entity; // this player entity in c_entities array // use this to ref main player
public $c_entities; //
public function __construct() {
// Set non-blocking mode for STDOUT
stream_set_blocking(STDOUT, false);
// Set non-blocking mode for STDIN
stream_set_blocking(STDIN, false);
$this->stdin = fopen('php://stdin', 'r');
$this->stdout = fopen('php://stdout', 'w');
$this->c_rooms = new c_rooms();
$this->c_entities = new c_entities();
$this->c_entity = $this->c_entities->add();
// $this->c_entity->set("local", true);
$this->c_entity->set_local();
$this->c_entity->set("name", "Test Player");
$this->c_entity->set("desc", "This is a test player.");
// $this->welcome();
}
// set starting room
public function set_starting_room($uid) {
echo "set_starting_room: " . $uid . "\n";
$this->c_entity->set("cur room", $uid);
}
// create and place a random npc
public function create_random_npc()
{
$npc = $this->c_entities->add();
// pick a random name //
$name = "NPC " . rand(1, 1000);
$npc->set("name", $name);
$npc->set("desc", "This is a test NPC.");
$npc->set("cur room", $this->c_rooms->get_random()->uid);
$npc->set("npc", true);
}
public function random_move_npcs()
{
// echo "random_move_npcs\n";
// go through each non local entity and move it randomly
foreach($this->c_entities->entities as $entity_uid => $entity) {
// echo "random_move_npcs: " . $entity->get("name") . "\n";
if($entity->get("npc") == true) {
// echo "random_move_npcs: " . $entity->get("name") . "\n";
// random 10% chance of moving
if(rand(1, 10) != 1) {
continue;
}
$this->move_dir($this->expand_cardinal(array_rand($this->c_rooms->get_exits($entity->get("cur room")))), $entity);
}
}
}
public function set($key, $value) {
$this->data[$key] = $value;
}
public function get($key) {
if (isset($this->data[$key]) )
return $this->data[$key];
}
public function out($msg) { // moved to entity so our npcs can process messages
fwrite($this->stdout, $msg);
}
public function move_dir($dir, $entity = null)
{
if($entity == null) {
$entity = $this->c_entity;
}
$cur_room_uid = $entity->get("cur room");
$exits = $this->c_rooms->get_exits($cur_room_uid);
if(isset($exits[$dir]) == false) {
// if is local player, show message
// if($entity->get("local") == true)
// $this->out("You can't go that way.\n");
$entity->out("You can't go that way.\n");
$entity->do_prompt();
return false;
}
// send a message to current room local player that this entity is leaving
$entities_in_room = $this->c_entities->get_all_in_room($cur_room_uid);
foreach($entities_in_room as $entity_uid => $entity2) {
/*
if($entity2->get("local") == true) {
$this->c_entity->out($entity->get("name") . " leaves the room to the " . $dir . ".\n");
}
*/
// if entity not c_entity (match by uid)
if($entity2->uid != $entity->uid) {
$entity2->out($entity->get("name") . " leaves the room to the " . $dir . ".\n");
$entity2->do_prompt();
}
}
$entity->set("cur room", $exits[$dir]);
// show message
$entity->out("You move " . $dir . ".\n\n");
// get all entities in room
// if our moving entity isn't a local player, and it is moving into the room as the local player, emit the message to local player
$entities_in_room = $this->c_entities->get_all_in_room($exits[$dir]);
foreach($entities_in_room as $entity_uid => $entity2) {
/*
if($entity2->get("local") == true) {
$this->out($entity->get("name") . " enters the room from the " . $dir . ".\n");
}
*/
// if entity not c_entity (match by uid)
if($entity2->uid != $entity->uid) {
$entity2->out($entity->get("name") . " enters the room.\n"); // should find opp direction and output that
$entity2->do_prompt();
}
}
return true;
}
public function do_look()
{
$cur_room_uid = $this->c_entity->get("cur room");
$cur_room = $this->c_rooms->get($cur_room_uid);
// output room name and desc
$this->c_entity->out("\n");
$this->c_entity->out("You are in " . $cur_room->get('name') . "\n");
$this->c_entity->out("-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-=-\n");
$this->c_entity->out($cur_room->get('desc') . "\n");
$this->c_entity->out("Exits: ");
// get exits
foreach($cur_room->get('exits') as $exit_direction => $exit_room_uid) {
$this->out($exit_direction . " ");
}
$this->out("\n");
// get entities in room
$entities_in_room = $this->c_entities->get_all_in_room($cur_room_uid);
foreach($entities_in_room as $entity_uid => $entity) {
// if entity not c_entity (match by uid)
if($entity->uid != $this->c_entity->uid) {
$this->c_entity->out($entity->get("name") . " is here.\n");
}
}
$this->c_entity->do_prompt();
}
public function expand_cardinal($dir)
{
switch($dir) {
case 'n': return 'north';
case 's': return 'south';
case 'e': return 'east';
case 'w': return 'west';
case 'u': return 'up';
case 'd': return 'down';
case 'ne': return 'northeast';
case 'nw': return 'northwest';
case 'se': return 'southeast';
case 'sw': return 'southwest';
}
return $dir;
}
public function cmd_is_dir($dir)
{
switch($dir) {
case 'north': case 'n':
case 'south': case 's':
case 'east': case 'e':
case 'west': case 'w':
case 'up': case 'u':
case 'down': case 'd':
case 'northeast': case 'ne':
case 'northwest': case 'nw':
case 'southeast': case 'se':
case 'southwest': case 'sw':
return true;
}
}
public function handleCommand($command) {
// trim
$command = trim($command);
// convert cardinal
$command = $this->expand_cardinal($command);
// if $command is an available exit direction, move that way
$cur_room_uid = $this->c_entity->get("cur room");
$exits = $this->c_rooms->get_exits($cur_room_uid);
if($this->cmd_is_dir($command))
if(isset($exits[$command])) {
$this->move_dir($command);
$this->do_look();
} else {
$this->c_entity->out("You can't go that way.\n");
$this->c_entity->do_prompt();
}
if(!$this->cmd_is_dir($command))
{
// process commands from user when they press enter
switch ($command) {
case 'help': case 'h': case '?':
$help = "\n\n";
$help .= "---\n";
$help .= "Showing help for *TestPluginGame*\n";
$help .= "Available commands:\n";
$help .= " directions: as specified by the room you are in\n";
$help .= " look: look around the room\n";
$help .= " show rooms: show all rooms (debug)\n";
$help .= " quit: quit the game\n";
$help .= "---\n\n";
$this->c_entity->out($help);
$this->c_entity->do_prompt();
break;
case 'show rooms':
$this->c_rooms->show();
$this->c_entity->do_prompt();
break;
case 'look': case 'l':
$this->do_look();
break;
default:
if($command != "quit")
$this->out("Invalid command. Type 'help' for a list of commands.\n");
break;
} // end switch
}
}
public function welcome() {
// triggered by main loop when code requests a welcome
$welcome = "\n\n\n\n";
$welcome .= "-- NonBlockingCLI test server --\n";
$welcome .= "Welcome to the NonBlockingCLI test server.\n";
$welcome .= "Type 'h' or 'help' for a list of commands.\n";
$welcome .= "Type 'quit' to exit.\n";
$welcome .= "\n\n";
$this->c_entity->out($welcome);
$this->do_look();
}
public function run() {
// this loop runs at main class speed
$this->run_loop_1(); // output current time every second
$this->run_ai_movement_loop(); // run every second
// .. can add other loops. Use run_loop_1 as an example.
}
public function run_ai_movement_loop()
{
$loop_key = 2; // for $this->next_run and $this->loop_running
if (!isset($this->next_run[$loop_key])) {
$this->next_run[$loop_key] = 0.0; // initialize // example of potential values: 1 second = 1.0, 1.5 seconds = 1.5, etc.
$this->loop_running[$loop_key] = false;
}
if ($this->loop_running[$loop_key] || microtime(true) < $this->next_run[$loop_key])
return;
$this->loop_running[$loop_key] = true;
// do:
// echo "ai loop\r\n";
$this->random_move_npcs();
// set pace of plugin loop (runs independent of main loop)
// store our next run time in a variable
$this->next_run[$loop_key] = microtime(true) + 5.0; // 1 second = 1.0, 1.5 seconds = 1.5, etc.
// set loop as not running
$this->loop_running[$loop_key] = false;
}
public function run_loop_1() {
$loop_key = 1; // for $this->next_run and $this->loop_running
// we want to take a slot in next_run in case we have other loops.
if (!isset($this->next_run[$loop_key])) {
$this->next_run[$loop_key] = 0.0; // initialize // example of potential values: 1 second = 1.0, 1.5 seconds = 1.5, etc.
$this->loop_running[$loop_key] = false;
}
// check if we should run this time
if ($this->loop_running[$loop_key] || microtime(true) < $this->next_run[$loop_key]) {
return;
}
// set loop as running
$this->loop_running[$loop_key] = true;
$output = "Current time: " . date('Y-m-d H:i:s') . "\n";
$this->c_entity->out($output);
$this->c_entity->do_prompt();
// set pace of plugin loop (runs independent of main loop)
// store our next run time in a variable
$this->next_run[$loop_key] = microtime(true) + 10.0; // 1 second = 1.0, 1.5 seconds = 1.5, etc.
// set loop as not running
$this->loop_running[$loop_key] = false;
}
}
## EXAMPLE: ##
// create plugins
$plugin_a = new my_plugin();
// create main object that controls the plugins
$cli = new nb_cli();
// add plugins to main controller
$cli->add_plugin($plugin_a);
// add rooms from array
$plugin_a->c_rooms->add_from_array($rooms_arr);
$plugin_a->set_starting_room($plugin_a->c_rooms->get_start());
// create some random npcs
for ($i = 0; $i < 10; $i++)
$plugin_a->create_random_npc();
// run our main controller which will run the plugins welcome() functions
$cli->welcome();
// run our main controller which will run the plugins run() functions in a loop
// maximum speed is set in main controller, but each plugin can time their own runs
$cli->run();
?>
|