1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/hubzilla_ynh.git synced 2024-09-03 19:26:21 +02:00
hubzilla_ynh/sources/addon/chess/chess.php
2016-06-22 23:26:29 +05:30

920 lines
No EOL
38 KiB
PHP

<?php
/**
*
* Name: Chess
* Description: Hubzilla plugin for decentralized, identity-aware chess games powered by chessboard.js
* Version: 0.8.3
* Author: Andrew Manning <https://grid.reticu.li/channel/andrewmanning/>
* MinVersion: 1.3.3
*
*/
define ( 'ACTIVITY_OBJ_CHESSGAME', NAMESPACE_ZOT . '/activity/chessgame' );
/**
* @brief Return the current plugin version
*
* @return string Current plugin version
*/
function chess_get_version() {
return '0.8.3';
}
function chess_load() {
// Control the page composition by loading a custom layout
register_hook('feature_settings', 'addon/chess/chess.php', 'chess_settings');
register_hook('feature_settings_post', 'addon/chess/chess.php', 'chess_settings_post');
register_hook('load_pdl', 'addon/chess/chess.php', 'chess_load_pdl');
}
function chess_unload() {
unregister_hook('feature_settings', 'addon/chess/chess.php', 'chess_settings');
unregister_hook('feature_settings_post', 'addon/chess/chess.php', 'chess_settings_post');
unregister_hook('load_pdl', 'addon/chess/chess.php', 'chess_load_pdl');
}
function chess_install() {
info('Chess plugin installed successfully');
logger('Chess plugin installed successfully');
}
function chess_uninstall() {
info('Chess plugin uninstalled successfully');
logger('Chess plugin uninstalled successfully');
}
// Required in order for the plugin to return webpages at /chess as if it were
// a subfolder in /mod
function chess_module() {}
/**
* @brief Defines the widget for the page layout, providing the game controls
*
* @return string HTML content of the aside region
*/
function widget_chess_controls() {
$which = null;
$owner = false;
if(argc() > 1) {
$which = argv(1);
if(local_channel()) {
$channel = App::get_channel();
if ($channel['channel_address'] === $which) {
$owner = true;
}
}
}
if(! $which) {
if(local_channel()) {
$channel = App::get_channel();
if($channel && $channel['channel_address'])
$which = $channel['channel_address'];
$owner = true;
}
}
$observer = App::get_observer();
$games = null;
if($which) {
$g = chess_get_games($observer, $which);
$games = $g['games'];
}
$historyviewer = false;
$gameinfo = null;
if (argc() > 2 && argv(2) !== 'new') {
$historyviewer = true;
$gameinfo = chess_get_info($observer,argv(2));
}
$o .= replace_macros(get_markup_template('chess_controls.tpl', 'addon/chess'), array(
'$owner' => $owner,
'$channel' => $which, //$channel['channel_address'],
'$games' => $games,
'$gameinfo' => $gameinfo,
'$historyviewer' => $historyviewer,
'$settings' => chess_game_settings(),
'$version' => '<a href="https://github.com/redmatrix/hubzilla-addons/">v'.chess_get_version().'</a>'
));
return $o;
}
/**
* @brief Set the layout for page composition, defining the aside region as an
* instance of the controls widget
*
* @return null
*/
function chess_load_pdl($a, &$b) {
if ($b['module'] === 'chess') {
$b['layout'] = '
[region=aside]
[widget=chess_controls][/widget]
[/region]
';
}
}
/**
* @brief Executes prior to page generation or $_REQUEST variables are parsed
*
* @return null
*/
function chess_init($a) {}
/**
* @brief This function provides the API endpoints, primarily called by the
* JavaScript functions via $.post() calls.
*
* @return json JSON-formatted structures with a "status" indicator for success
* as well as other requested data
*/
function chess_post(&$a) {
if (argc() > 1) {
switch (argv(1)) {
// API: /chess/settings
// Updates game settings for the observer
case 'settings':
$observer = App::get_observer();
$settings = (x($_POST,'settings') ? $_POST['settings'] : null );
$settings = json_decode($settings, true);
if (!isset($settings['notify_enabled'])) {
json_return_and_die(array('errormsg' => 'Invalid settings', 'status' => false));
}
$notify_enable = intval($settings['notify_enabled']);
set_xconfig($observer['xchan_hash'],'chess','notifications',$notify_enable);
json_return_and_die(array('status' => true));
// API: /chess/resume
// Resumes a game specified by "game_id" allowing further moves
case 'resume':
$observer = App::get_observer();
$game_id = (x($_POST,'game_id') ? $_POST['game_id'] : '' );
$g = chess_get_game($game_id);
if(!$g['status']) {
json_return_and_die(array('errormsg' => 'Invalid game', 'status' => false));
}
// Verify that observer is a valid player
$game = json_decode($g['game']['obj'], true);
if(!in_array($observer['xchan_hash'], $game['players'])) {
json_return_and_die(array('errormsg' => 'You are not a valid player', 'status' => false));
}
$success = chess_resume_game($g['game']);
if(!$success) {
json_return_and_die(array('errormsg' => 'Error resuming game', 'status' => false));
} else {
json_return_and_die(array('status' => true));
}
// API: /chess/end
// Ends a game specified by "game_id" preventing further moves
case 'end':
$observer = App::get_observer();
$game_id = (x($_POST,'game_id') ? $_POST['game_id'] : '' );
$g = chess_get_game($game_id);
if(!$g['status']) {
json_return_and_die(array('errormsg' => 'Invalid game', 'status' => false));
}
// Verify that observer is a valid player
$game = json_decode($g['game']['obj'], true);
if(!in_array($observer['xchan_hash'], $game['players'])) {
json_return_and_die(array('errormsg' => 'You are not a valid player', 'status' => false));
}
$success = chess_end_game($g['game']);
if(!$success) {
json_return_and_die(array('errormsg' => 'Error ending game', 'status' => false));
} else {
json_return_and_die(array('status' => true));
}
// API: /chess/delete
// Deletes a game specified by "game_id"
case 'delete':
if (!local_channel()) {
json_return_and_die(array('errormsg' => 'Must be local channel.', 'status' => false));
}
$channel = App::get_channel();
$game_id = (x($_POST,'game_id') ? $_POST['game_id'] : '' );
$d = chess_delete_game($game_id, $channel);
if(!$d['status']) {
json_return_and_die(array('errormsg' => 'Error deleting game', 'status' => false));
} else {
json_return_and_die(array('status' => true));
}
// API: /chess/revert
// Reverts a game specified by "game_id" to a previous board position
// specified by the "mid" of the child post of the original game post
// in the item table
// TODO: Determine why the board position in the game item is not actually
// being reverted
case 'revert':
$observer = App::get_observer();
$game_id = $_POST['game_id'];
$g = chess_get_game($game_id);
if(!$g['status']) {
json_return_and_die(array('errormsg' => 'Invalid game', 'status' => false));
}
// Verify that observer is a valid player
$game = json_decode($g['game']['obj'], true);
if(!in_array($observer['xchan_hash'], $game['players'])) {
json_return_and_die(array('errormsg' => 'You are not a valid player', 'status' => false));
}
$active = ($game['active'] === $observer['xchan_hash'] ? true : false);
if(!$active) {
json_return_and_die(array('errormsg' => 'It is not your turn', 'status' => false));
}
$r = chess_revert_position($g['game'], $observer, $_POST['mid']);
if(!$r['status']) {
json_return_and_die(array('errormsg' => 'Error reverting game', 'status' => false));
}
json_return_and_die(array('status' => true));
// API: /chess/history
// Retrieves all the board positions for a game in order to populate the
// history viewer in the control panel
case 'history':
$observer = App::get_observer();
$game_id = $_POST['game_id'];
$g = chess_get_game($game_id);
if(!$g['status']) {
json_return_and_die(array('errormsg' => 'Invalid game', 'status' => false));
}
// Verify that observer is a valid player
$game = json_decode($g['game']['obj'], true);
if(!in_array($observer['xchan_hash'], $game['players'])) {
json_return_and_die(array('errormsg' => 'You are not a valid player', 'status' => false));
}
$player = array_search($observer['xchan_hash'], $game['players']);
$h = chess_get_history($g['game']);
if(!$h['status']) {
json_return_and_die(array('errormsg' => 'Error retrieving game history', 'status' => false));
}
json_return_and_die(array('history' => $h['history'], 'status' => true));
// API: /chess/update
// Updates a game specified by "game_id" with a new board position specified
// by "newPosFEN" in FEN-format
case 'update':
$observer = App::get_observer();
$game_id = $_POST['game_id'];
$g = chess_get_game($game_id);
if(!$g['status']) {
json_return_and_die(array('errormsg' => 'Invalid game', 'status' => false));
}
// Verify that observer is a valid player
$game = json_decode($g['game']['obj'], true);
if(!in_array($observer['xchan_hash'], $game['players'])) {
json_return_and_die(array('errormsg' => 'You are not a valid player', 'status' => false));
}
$player = array_search($observer['xchan_hash'], $game['players']);
$active = ($game['active'] === $game['players'][$player] ? true : false);
json_return_and_die(array('position' => $game['position'], 'myturn' => $active, 'ended' => $game['ended'], 'status' => true));
// API: /chess/move
// Adds a new board position by creating a child post for the original
// game item.
case 'move':
$observer = App::get_observer();
$game_id = $_POST['game_id'];
$newPosFEN = $_POST['newPosFEN'];
$g = chess_get_game($game_id);
if(!$g['status']) {
notice(t('Invalid game.') . EOL);
json_return_and_die(array('errormsg' => 'Invalid game ID', 'status' => false));
}
// Verify that observer is a valid player
$game = json_decode($g['game']['obj'], true);
if(!in_array($observer['xchan_hash'], $game['players'])) {
notice(t('You are not a player in this game.') . EOL);
goaway('/chess');
}
$player = array_search($observer['xchan_hash'], $game['players']);
$color = $game['colors'][$player];
$active = ($game['active'] === $observer['xchan_hash'] ? true : false);
if(!$active) {
json_return_and_die(array('errormsg' => 'It is not your turn', 'status' => false));
}
if(x($game,'ended') && intval($game['ended']) === 1) {
json_return_and_die(array('errormsg' => 'The game is over', 'status' => false));
}
$move = chess_make_move($observer, $newPosFEN, $g['game']);
if ($move['status']) {
$active_xchan = ($game['players'][0] === $observer['xchan_hash'] ? $game['players'][1] : $game['players'][0]);
if(chess_set_position(chess_get_game($game_id)['game'], $newPosFEN)) {
chess_set_active(chess_get_game($game_id)['game'], $active_xchan);
}
json_return_and_die(array('status' => true));
} else {
json_return_and_die(array('errormsg' => 'Move failed', 'status' => false));
}
default:
break;
}
}
if (argc() > 2) {
switch (argv(2)) {
// API: /chess/[channelname]/new/
// This endpoint handles the new game form submission and creates a new
// game between two channels specified by the standard ACL
case 'new':
if (!local_channel()) {
notice(t('You must be a local channel to create a game.') . EOL);
return;
}
// Ensure ACL specifies exactly one other channel
$channel = App::get_channel();
$acl = new Zotlabs\Access\AccessList($channel);
$acl->set_from_array($_REQUEST);
$perms = $acl->get();
$allow_cid = expand_acl($perms['allow_cid']);
$valid = 0;
if(count($allow_cid) > 1) {
foreach($allow_cid as $allow) {
if($allow == $channel['channel_hash'])
continue;
$valid ++;
}
}
if ($valid != 1) {
notice(t('You must select one opponent that is not yourself.') . EOL);
return;
} else {
info(t('Creating new game...') . EOL);
// Get the game owner's color choice
$color = '';
if ($_POST['color'] === 'white' || $_POST['color'] === 'black') {
$color = $_POST['color'];
} else {
notice(t('You must select white or black.') . EOL);
return;
}
$game = chess_create_game($channel, $color, $acl);
if ($game['status']) {
goaway('/chess/' . $channel['channel_address'] . '/' . $game['item']['resource_id']);
} else {
notice(t('Error creating new game.') . EOL);
}
return;
}
default:
break;
}
}
}
/**
* @brief Outputs the main content of the page, depending on the URL
*
* @return string HTML content
*/
function chess_content($a) {
// Include the custom CSS and JavaScript necessary for the chess board
head_add_css('/addon/chess/view/css/chessboard.css');
head_add_js('/addon/chess/view/js/chessboard.js');
// If the user is not a local channel, then they must use a URL like /chess/localchannel
// to specify which local channel "chess host" they are visiting
$which = null;
if(argc() > 1) {
$which = argv(1);
$user = q("select channel_id from channel where channel_address = '%s' and channel_removed = 0 limit 1",
dbesc($which)
);
if(!$user) {
notice( t('Requested channel is not available.') . EOL );
App::$error = 404;
return;
}
}
if(! $which) {
if(local_channel()) {
$channel = App::get_channel();
if($channel && $channel['channel_address'])
$which = $channel['channel_address'];
}
}
if(! $which) {
notice( t('You must select a local channel /chess/channelname') . EOL );
return;
}
if (argc() > 2) {
switch (argv(2)) {
case 'new':
if (!local_channel()) {
notice(t('You must be logged in to see this page.') . EOL);
return;
}
$acl = new Zotlabs\Access\AccessList(App::get_channel());
$channel_acl = $acl->get();
require_once('include/acl_selectors.php');
$channel = App::get_channel();
$o = replace_macros(get_markup_template('chess_new.tpl', 'addon/chess'), array(
'$acl' => populate_acl($channel_acl, false),
'$channel' => $channel['channel_address']
));
return $o;
default:
// argv(2) is the resource_id for an existing game
// argv(1) should be the owner channel of the game
$owner = argv(1);
$hash = q("select channel_hash from channel where channel_address = '%s' and channel_removed = 0 limit 1",
dbesc($owner)
);
$owner_hash = $hash[0]['channel_hash'];
$game_id = argv(2);
$observer = App::get_observer();
$g = chess_get_game($game_id);
if(!$g['status'] || $g['game']['owner_xchan'] !== $owner_hash) {
notice(t('Invalid game.') . EOL);
return;
}
// Verify that observer is a valid player
$game = json_decode($g['game']['obj'], true);
if(!in_array($observer['xchan_hash'], $game['players'])) {
notice(t('You are not a player in this game.') . EOL);
goaway('/chess');
}
$player = array_search($observer['xchan_hash'], $game['players']);
$color = $game['colors'][$player];
$active = ($game['active'] === $game['players'][$player] ? true : false);
$game_ended = ((!x($game,'ended') || $game['ended'] === 0) ? 0 : 1);
$notify = intval(get_xconfig($observer['xchan_hash'],'chess','notifications'));
logger('xconfig notifications: ' . $notify);
$o = replace_macros(get_markup_template('chess_game.tpl', 'addon/chess'), array(
'$myturn' => ($active ? 'true' : 'false'),
'$active' => $active,
'$color' => $color,
'$game_id' => $game_id,
'$position' => $game['position'],
'$ended' => $game_ended,
'$notifications' => $notify
));
// TODO: Create settings panel to set the board size and eventually the board theme
// and other customizations
return $o;
}
}
// If the URL was simply /chess, then if the script reaches this point the
// user is a local channel, so load any games they may have as well as a board
// they can move pieces around on without storing the moves anywhere
$o .= replace_macros(get_markup_template('chess.tpl', 'addon/chess'), array(
'$color' => 'white'
));
return $o;
}
/**
* @brief Create a new game by generating a new item table record as a standard
* post. This will propagate to the other player and provide a link to begin playing
*
* @return array Status and parameters of the new game post
*/
function chess_create_game($channel, $color, $acl) {
$resource_type = 'chess';
// Generate unique resource_id using the same method as item_message_id()
do {
$dups = false;
$resource_id = random_string(5);
$r = q("SELECT mid FROM item WHERE resource_id = '%s' AND resource_type = '%s' AND uid = %d LIMIT 1",
dbesc($resource_id),
dbesc($resource_type),
intval($channel['channel_id'])
);
if (count($r))
$dups = true;
} while ($dups == true);
$ac = $acl->get();
$mid = item_message_id();
$arr = array(); // Initialize the array of parameters for the post
$objtype = ACTIVITY_OBJ_CHESSGAME;
$perms = $acl->get();
$allow_cid = expand_acl($perms['allow_cid']);
$player2 = null;
if(count($allow_cid)) {
foreach($allow_cid as $allow) {
if($allow === $channel['channel_hash'])
continue;
$player2 = $allow;
}
}
$players = array($channel['channel_hash'], $player2);
$object = json_encode(array(
'id' => z_root() . '/chess/game/' . $resource_id,
'players' => $players,
'colors' => array($color, ($color === 'white' ? 'black' : 'white')),
'active' => ($color === 'white' ? $players[0] : $players[1]),
'position' => 'start',
'version' => chess_get_version() // Potential compatability issues
));
$item_hidden = 0; // TODO: Allow form creator to send post to ACL about new game automatically
$game_url = z_root() . '/chess/' . $channel['channel_address'] . '/' . $resource_id;
$arr['aid'] = $channel['channel_account_id'];
$arr['uid'] = $channel['channel_id'];
$arr['mid'] = $mid;
$arr['parent_mid'] = $mid;
$arr['item_hidden'] = $item_hidden;
$arr['resource_type'] = $resource_type;
$arr['resource_id'] = $resource_id;
$arr['owner_xchan'] = $channel['channel_hash'];
$arr['author_xchan'] = $channel['channel_hash'];
// Store info about the type of chess item using the "title" field
// Other types include 'move' for children items but may in the future include
// additional types that will determine how the "object" field is interpreted
$arr['title'] = 'game';
$arr['allow_cid'] = $ac['allow_cid'];
$arr['item_wall'] = 1;
$arr['item_origin'] = 1;
$arr['item_thread_top'] = 1;
$arr['item_private'] = intval($acl->is_private());
$arr['verb'] = ACTIVITY_POST;
$arr['obj_type'] = $objtype;
$arr['obj'] = $object;
$arr['body'] = '[table][tr][td][h1]New Chess Game[/h1][/td][/tr][tr][td][zrl='.$game_url.']Click here to play[/zrl][/td][/tr][/table]';
$post = item_store($arr);
$item_id = $post['item_id'];
if ($item_id) {
Zotlabs\Daemon\Master::Summon(['Notifier','activity',$item_id]);
return array('item' => $arr, 'status' => true);
} else {
return array('item' => null, 'status' => false);
}
}
/**
* @brief Create a new move in the game by generating a child item for the game post
*
* @param $observer Authenticated observer (remote or local channel) viewing the page
* @param $newPosFEN New board position in FEN-format
* @param $g Game post item table record with all the game information
* @return array Success status and array of new post data
*/
function chess_make_move($observer, $newPosFEN, $g) {
$resource_type = 'chess';
$resource_id = $g['resource_id'];
$mid = item_message_id();
$arr = array(); // Initialize the array of parameters for the post
$objtype = ACTIVITY_OBJ_CHESSGAME;
$object = json_encode(array(
'id' => z_root() . '/chess/game/' . $resource_id,
'position' => $newPosFEN, // Store the new board position in FEN notation
'version' => chess_get_version() // Potential compatability issues
));
$item_hidden = 0; // TODO: Allow form creator to send post to ACL about new game automatically
$r = q("select channel_address from channel where channel_id = %d limit 1",
intval($g['uid'])
);
$channel_address = '';
if($r) {
$channel_address = $r[0]['channel_address'] ;
}
$arr['aid'] = $g['aid'];
$arr['uid'] = $g['uid'];
$arr['mid'] = $mid;
$arr['parent_mid'] = $g['mid'];
$arr['item_hidden'] = $item_hidden;
$arr['resource_type'] = $resource_type;
$arr['resource_id'] = $resource_id; // Game ID
$arr['owner_xchan'] = $g['owner_xchan']; // Tracks the owner of the game
$arr['author_xchan'] = $observer['xchan_hash']; // Denotes which player made this move
// Store info about the type of chess item using the "title" field
// Other types include 'move' for children items but may in the future include
// additional types that will determine how the "object" field is interpreted
$arr['title'] = 'move';
$arr['item_wall'] = 1;
$arr['item_origin'] = 1;
$arr['item_thread_top'] = 0;
$arr['item_private'] = 1;
$arr['verb'] = ACTIVITY_POST;
$arr['obj_type'] = $objtype;
$arr['obj'] = $object;
$arr['body'] = 'New position (FEN format): ' . $newPosFEN;
$post = item_store($arr);
$item_id = $post['item_id'];
if ($item_id) {
Zotlabs\Daemon\Master::Summon(['Notifier','activity',$item_id]);
return array('item' => $arr, 'status' => true);
} else {
return array('item' => null, 'status' => false);
}
}
/**
* @brief Change the game item to specify which player should take the next turn
*
* @param $xchan Unique hash associated with which channel should take the next turn
* @param $g Game post item table record with all the game information
* @return boolean Success of game item update
*/
function chess_set_active($g, $xchan) {
$game = json_decode($g['obj'], true);
$game['active'] = $xchan;
if(! $game['id'])
$game['id'] = $game['resource_id'];
$gameobj = json_encode($game);
$r = q("UPDATE item set obj = '%s' WHERE mid = '%s' AND resource_type = '%s'",
dbesc($gameobj),
dbesc($g['mid']),
dbesc('chess')
);
return $r;
}
/**
* @brief Updates the game item with the latest board position
*
* @param $position New board position in FEN-format
* @param $g Game post item table record with all the game information
* @return array Success of game item update
*/
function chess_set_position($g, $position) {
$game = json_decode($g['obj'], true);
$game['position'] = $position;
if(! $game['id'])
$game['id'] = $game['resource_id'];
$gameobj = json_encode($game);
$r = q("UPDATE item set obj = '%s' WHERE mid = '%s' AND resource_type = '%s'",
dbesc($gameobj),
dbesc($g['mid']),
dbesc('chess')
);
return $r;
}
/**
* @brief Retrieve the game item data structure
*
* @param $game_id Unique game ID string
* @return array Success of retrieval and game item
*/
function chess_get_game($game_id) {
$g = q("SELECT * FROM item WHERE resource_id = '%s' AND resource_type = '%s' and mid = parent_mid AND item_deleted = 0 LIMIT 1",
dbesc($game_id),
dbesc('chess')
);
if (!$g) {
return array('game' => null, 'status' => false);
} else {
return array('game' => $g[0], 'status' => true);
}
}
/**
* @brief Retrieve all board positions of a game
*
* @param $g Game post item table record with all the game information
* @return array Success of retrieval and game history
*/
function chess_get_history($g) {
$parentmid = $g['mid'];
$moves = q("SELECT mid,obj,author_xchan FROM item WHERE resource_type = '%s' AND resource_id = '%s' AND parent_mid = '%s' AND mid != parent_mid order by id",
dbesc('chess'),
dbesc($g['resource_id']),
dbesc($parentmid)
);
if (!$moves) {
return array('history' => null, 'status' => false);
} else {
return array('history' => $moves, 'status' => true);
}
}
/**
* @brief Revert the game to a previous board position
*
* @param $g Game post item table record with all the game information
* @param $observer Authenticated observer (remote or local channel) viewing the page
* @return array Success of board position reversion
*/
function chess_revert_position($g, $observer, $mid) {
$m = q("SELECT obj FROM item WHERE resource_type = '%s' AND resource_id = '%s' AND mid = '%s' LIMIT 1",
dbesc('chess'),
dbesc($g['resource_id']),
dbesc($mid)
);
if (!$m) {
return array('status' => false);
} else {
$gameobj = json_decode($g['obj'], true);
$moveobj = json_decode($m[0]['obj'], true);
$move = chess_make_move($observer, $moveobj['position'], $g);
if ($move['status']) {
if(chess_set_position($g, $moveobj['position'])) {
$active_xchan = ($gameobj['players'][0] === $observer['xchan_hash'] ? $gameobj['players'][1] : $gameobj['players'][0]);
chess_set_active($g, $active_xchan);
return array('status' => true);
} else {
return array('status' => false);
}
} else {
return array('status' => false);
}
}
}
/**
* @brief Retrieve a list of games in which the observer is a participant, separating
* lists of those owned and those not owned
*
* @param $owner_address The channel name taken from the URL /chess/[channelname]
* @param $observer Authenticated observer (remote or local channel) viewing the page
* @return array Success of games retrieval and the games data
*/
function chess_get_games($observer, $owner_address) {
$g = [];
$g['owner_active'] = $g['player_active'] = $g['owner_ended'] = $g['player_ended'] = [];
$hash = q("select channel_hash from channel where channel_address = '%s' and channel_removed = 0 limit 1",
dbesc($owner_address)
);
if (!$hash) {
$g['owner_active'] = $g['player_active'] = $g['owner_ended'] = $g['player_ended'] = null;
return array('games' => $g, 'status' => false);
}
$owner_hash = $hash[0]['channel_hash'];
// This is a potentially expensive query if there are many chess games
$games = q("SELECT * FROM item WHERE resource_type = '%s' AND title = '%s' AND owner_xchan = '%s' AND obj LIKE '%s' AND item_deleted = 0 order by id desc",
dbesc('chess'),
dbesc('game'),
dbesc($owner_hash),
dbesc('%' . $observer['xchan_hash'] . '%')
);
if (!$games) {
$g['owner_active'] = $g['player_active'] = $g['owner_ended'] = $g['player_ended'] = null;
return array('games' => $g, 'status' => false);
}
foreach($games as $game) {
$gameobj = json_decode($game['obj'], true);
// Get the names of the players
$info = chess_get_info($observer, $game['resource_id']);
// Determine opponent's name
$opponent_name = (($observer['xchan_hash'] === $gameobj['players'][0]) ? $info['players'][1] : $info['players'][0]);
$active = (($observer['xchan_hash'] === $gameobj['active']) ? true : false);
$date = array_shift(split(' ',$game['created']));
if($game['owner_xchan'] === $observer['xchan_hash']) {
if(!x($gameobj,'ended') || $gameobj['ended'] === 0) {
$g['owner_active'][] = array('plink' => $game['plink'], 'game_id' => $game['resource_id'], 'date' => $date, 'opponent' => $opponent_name, 'active' => $active, 'obj' => $gameobj);
} else {
$g['owner_ended'][] = array('plink' => $game['plink'], 'game_id' => $game['resource_id'], 'date' => $date, 'opponent' => $opponent_name, 'active' => $active, 'obj' => $gameobj);
}
} elseif (in_array($observer['xchan_hash'], $gameobj['players'])) {
if(!x($gameobj,'ended') || $gameobj['ended'] === 0) {
$g['player_active'][] = array('plink' => $game['plink'], 'game_id' => $game['resource_id'], 'date' => $date, 'opponent' => $opponent_name, 'active' => $active, 'obj' => $gameobj);
} else {
$g['player_ended'][] = array('plink' => $game['plink'], 'game_id' => $game['resource_id'], 'date' => $date, 'opponent' => $opponent_name, 'active' => $active, 'obj' => $gameobj);
}
}
}
$g['owner_active'] = ((empty($g['owner_active'])) ? null : $g['owner_active']);
$g['owner_ended'] = ((empty($g['owner_ended'])) ? null : $g['owner_ended']);
$g['player_active'] = ((empty($g['player_active'])) ? null : $g['player_active']);
$g['player_ended'] = ((empty($g['player_ended'])) ? null : $g['player_ended']);
return array('games' => $g, 'status' => true);
}
/**
* @brief Delete a chess game using the standard drop_item method for posts in the
* item table
*
* @param $game_id unique ID of the game to be deleted
* @param $channel The authenticated local channel requesting the deletion
* @return array Success of deletion and item that was deleted
*/
function chess_delete_game($game_id, $channel) {
$items = q("SELECT id FROM item WHERE resource_type = '%s' AND resource_id = '%s' AND uid = %d AND item_deleted = 0 limit 1",
dbesc('chess'),
dbesc($game_id),
intval($channel['channel_id'])
);
if (!$items) {
return array('items' => null, 'status' => false);
} else {
$drop = drop_item($items[0]['id'],false,DROPITEM_NORMAL,true);
return array('items' => $items, 'status' => (($drop === 1) ? true : false));
}
}
/**
* @brief Ends a chess game by setting a game item object property. Assumes the
* permissions to perform this action are already verified
*
* @param $g Game post item table record with all the game information
* @return array Success of ending game
*/
function chess_end_game($g) {
$game = json_decode($g['obj'], true);
$game['ended'] = 1; // An active game will have ended = 0 or will not have "ended" at all
$gameobj = json_encode($game);
$r = q("UPDATE item set obj = '%s' WHERE mid = '%s' AND resource_type = '%s'",
dbesc($gameobj),
dbesc($g['mid']),
dbesc('chess')
);
return $r;
}
/**
* @brief Resumes a chess game by setting a game item object property. Assumes the
* permissions to perform this action are already verified
*
* @param $g Game post item table record with all the game information
* @return array Success of resuming game
* @todo Combine this with chess_end_game() with a 0/1 input parameter
*/
function chess_resume_game($g) {
$game = json_decode($g['obj'], true);
$game['ended'] = 0; // An active game will have ended = 0 or will not have "ended" at all
$gameobj = json_encode($game);
$r = q("UPDATE item set obj = '%s' WHERE mid = '%s' AND resource_type = '%s'",
dbesc($gameobj),
dbesc($g['mid']),
dbesc('chess')
);
return $r;
}
/**
* @brief Retrieve various info about a game, including the players' names and the
* permanent link to the game conversation
*
* @param $game_id unique ID of the game to be deleted
* @param $observer Authenticated observer (remote or local channel) viewing the page
* @return array Success of retrieval and the game info
*/
function chess_get_info($observer, $game_id) {
// Get the game by game_id and
$g = chess_get_game($game_id);
if (!$g) {
return array('players' => null, 'status' => false);
}
$game = json_decode($g['game']['obj'], true);
// If the observer is a player in the game, get the names of the players
if(in_array($observer['xchan_hash'], $game['players'])) {
$player_names = [];
foreach($game['players'] as $xchan_hash) {
$p = q("select xchan_name from xchan where xchan_hash = '%s' limit 1",
dbesc($xchan_hash)
);
if (!$p) {
return array('players' => null, 'status' => false);
}
$player_names[] = $p[0]['xchan_name'];
}
return array('players' => $player_names, 'plink' => $g['game']['plink'], 'status' => true);
} else {
return array('players' => null, 'plink' => null, 'status' => false);
}
}
/*
function chess_settings_post(&$a,&$b) {
$observer = App::get_observer();
if($_POST['chess-submit']) {
set_xconfig($observer['xchan_hash'],'chess','notifications',intval($_POST['notifications']));
info( t('Chess settings updated.') . EOL);
}
}
function chess_settings(&$a,&$s) {
if(! local_channel())
return;
$observer = App::get_observer();
$notifications = get_xconfig($observer['xchan_hash'],'chess','notifications');
$sc .= replace_macros(get_markup_template('field_checkbox.tpl'), array(
'$field' => array('notifications', t('Enable notifications'), $notifications, '', $yes_no),
));
$s .= replace_macros(get_markup_template('generic_addon_settings.tpl'), array(
'$addon' => array('chess', '<img src="addon/chess/chess.png" style="width:auto; height:1em; margin:-3px 5px 0px 0px;">' . t('Chess Settings'), '', t('Submit')),
'$content' => $sc
));
return;
}
*/
function chess_game_settings() {
$observer = App::get_observer();
$notifications = get_xconfig($observer['xchan_hash'],'chess','notifications');
$sc .= replace_macros(get_markup_template('field_checkbox.tpl'), array(
'$field' => array('chess_notify_enable', t('Enable notifications'), $notifications, '', $yes_no),
));
return $sc;
}