Nested PHP conditional statement structure issue - php

I am using Google Distance Matrix to calculate the distance between two locations for a taxi website. I need to test a condition to ascertain whether the one of the locations is a London airport and if so return a message rather than the calculating the cost of the trip.
It worked perfectly yesterday and then today ... the code has gone biserk. It is only returning the message if pick-up and drop off locations are both airports but if only one is, it is calculating the cost for the full distance which defeats my purpose. Below is the code structure and I am wondering whether I am using the wrong nested conditional syntax at line 39 when I start the strpos() test ....
if (isset($_POST['submitted'])):
$origin = urlencode($_POST['origin']);
$destination = urlencode($_POST['destination']);
// Insert encoded url variables
$url = "http://maps.googleapis.com/maps/api/distancematrix/json?origins=$origin&destinations=$destination&mode=driving&keyy={API KEY}";
$json = file_get_contents($url); // get the data from Google Maps API
$status = $result['rows'][0]['elements'][0]['status'];
if (status is OK):
// Calculate the distance
$status = ...;
$DistanceMetres = .....;
$Distance = .....; // converted to yards
// Calculate the Day Rate
....
// Calculate the Night Rate
....
// Calculate the Sunday Rate
....
// Calculate the Christmas & NYE Rate
....
// Set up variables for pick-up & drop-off locations
$toairport = $result['destination_addresses'][0];
$fromairport= $result['origin_addresses'][0];
if (distance is the minimum distance) {
echo the minimum cost of the trip
} else { //ie if the distance is more than the minimum distance
// Check to see if pick-up or drop-off destination is an airport
if (strpos($toairport, 'Heathrow') || strpos($toairport, 'Gatwick') || strpos($toairport, 'London Luton') || strpos($toairport, 'London City Airport') || strpos($fromairport, 'Heathrow') || strpos($fromairport, 'Gatwick') || strpos($fromairport, 'London Luton') || strpos($fromairport, 'London City Airport') === false) {
echo the cost of the trip
// But if at least one location is an airport
} else {
echo a message saying a special flat rate is available for airport transfers
}
}
else:
echo that status is not okay
endif;
else:
display input form
endif;

I suggest to implement the check as a function. For example
/**
* Check if the location is an airport.
*
* #param string $location
* #return bool
*/
function isAirport($location)
{
$airportList = ['Heathrow', 'Gatwick', 'London Luton', 'London City Airport'];
foreach ($airportList as $airport) {
if (strpos($location, $airport) !== false) {
return true;
}
}
return false;
}
Note that we compare strpos result with false using !== operator because strpos might return zero which is == to false.
Next, internal check might look like this
if (isAirport($toairport) || isAirport($fromairport)) {
echo 'a message saying a special flat rate is available for airport transfers'.PHP_EOL;
} else {
echo 'the cost of the trip'.PHP_EOL;
}
Note that we check that either $toairport are $fromairport` variables are airport locations. I guess this is what you want the script to do.
When we use function like this it makes significantly easy to read the script and understand what is it doing. Also it makes it easier to modify the script by adding new location or improving the logic. For example we might want to make the check case insensitive.

Related

Selecting one number from the range

I have a trivial question for you but I can not deal with the matches myself.
more precisely - I have a problem with writing a condition that will only be fulfilled when specific parameters occur.
say, I have three thresholds: 2000, 15325, 500000
each of them has an appropriate level
user points are, for example: 16,000
I want it to search for the appropriate level and assign it to the user after reaching the threshold
I unfortunately have a problem with the condition of fulfilling the activity, because the condition
user points> = point threshold
shows me all levels, but
user points <= point threshold
it does not show anything, it does not fulfill the assumption of the script
function chooseLevel($dbh) {
$userPoints = getPoints($dbh, 3);
$levels = getLevels($dbh);
foreach($levels as $required) {
if($userPoints >= $required["points"]) {
$level = $required["level"];
}
}
return $level;
}

Regex for Dutch telephone numbers

I'm definitely not the worst when it comes down to regex, but this one has got me stumped.
In short, this is the code I currently have.
$aNumbers = array(
'612345678',
'546123465',
'131234567',
'+31(0)612345678'
);
foreach($aNumbers as $sNumber) {
$aMatches = array();
$sNumber = preg_replace('/(\(0\)|[^\d]+)/', '', $sNumber);
preg_match('/(\d{1,2})?(\d{3})(\d{3})(\d{3})$/', $sNumber, $aMatches);
var_dump($sNumber);
var_dump($aMatches);
}
Simply put, I want to match specific formats for telephone numbers to ensure a unified display.
+31(0)612345678
+31(0)131234567
Both stripped would be without + and (0).
Cut down in parts:
31 6 123 456 78
Country Net Number
31 13 123 456 78
Country Net Number
Now, in some cases the +31 (or +1, +222) are optional. The 6 and 13 are always included, but as a fun twist, the following format is also possible:
31 546 123 456
Country Net Number
Is this even possible with regex?
I've answered a few of these types of questions, and my strategy is to identify certain portions of formatting or number relationships that convey meaning, and get rid of the rest.
One of my examples that parses non-NANP number formatting uses a list of valid area codes in the parsing expression, and identifies country code when present. It extracts the country code, area code, and then the rest of the number.
or your country, I am assuming the list of area/net/region codes in HansM's answer is either correct or easily replaceable, so I'll guess that this modification of a regex might be useful:
^[ -]*(\+31)?[ -]*[(0)]*[ -]*(7|43|32|45|33|49|39|31|47|34|46|41|90|44|351|353|358)[ -]*((?:\d[ -]*)+)
It will first match the country code, if it is present, and store it in back-reference 1, then ignore a single zero. It will then match one of the area/net/region codes and store it in back-reference 2. It will then get any number of digits (one or more), mixed with dashes (-) and/or spaces () and store those into back-reference 3
After this, you could parse the third numbering group for validity or further reformatting
I'm testing it on Regex 101, but I could use a list of acceptable and unacceptable input, and how it should be reformatted when acceptable...
[EDIT]
I've used this list of city codes for the Netherlands and modified the expression thusly:
^[ -]*(\+31)?[ -]*[(0)]*[ -]*([123457]0|23|24|26|35|45|71|73|570)[ -]*((?:\d[ -]*)+)
which performs the following parsing:
input (1) (2) (3)
--------------------- ------ ------ ---------------
0707123456 70 7123456
0267-123456 26 7-123456
0407-12 34 56 40 7-12 34 56
0570123456 570 123456
07312345 73 12345
+31(0)734423211 +31 73 4423211
but I still don't know if that's helpful for you
[EDIT 2]
Wikipedia has what appears to be a more comprehensive list of codes
010, 0111, 0113, 0114, 0115, 0117, 0118, 013, 015, 0161, 0162, 0164, 0165, 0166, 0167, 0168, 0172, 0174, 0180, 0181, 0182, 0183, 0184, 0186, 0187, 020, 0222, 0223, 0224, 0226, 0227, 0228, 0229, 023, 024, 0251, 0252, 0255, 026, 0294, 0297, 0299, 030, 0313, 0314, 0315, 0316, 0317, 0318, 0320, 0321, 033, 0341, 0342, 0343, 0344, 0345, 0346, 0347, 0348, 035, 036, 038, 040, 0411, 0412, 0413, 0416, 0418, 043, 045, 046, 0475, 0478, 0481, 0485, 0486, 0487, 0488, 0492, 0493, 0495, 0497, 0499, 050, 0511, 0512, 0513, 0514, 0515, 0516, 0517, 0518, 0519, 0521, 0522, 0523, 0524, 0525, 0527, 0528, 0529, 053, 0541, 0543, 0544, 0545, 0546, 0547, 0548, 055, 0561, 0562, 0566, 0570, 0571, 0572, 0573, 0575, 0577, 0578, 058, 0591, 0592, 0593, 0594, 0595, 0596, 0597, 0598, 0599, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079
which can be used in the code selection portion like this (if you'd prefer it to be more easily read and updated):
10|111|113|114|115|117|118|13|15|161|162|164|165|166|167|168|172|174|180|181|182|183|184|186|187|20|222|223|224|226|227|228|229|23|24|251|252|255|26|294|297|299|30|313|314|315|316|317|318|320|321|33|341|342|343|344|345|346|347|348|35|36|38|40|411|412|413|416|418|43|45|46|475|478|481|485|486|487|488|492|493|495|497|499|50|511|512|513|514|515|516|517|518|519|521|522|523|524|525|527|528|529|53|541|543|544|545|546|547|548|55|561|562|566|570|571|572|573|575|577|578|58|591|592|593|594|595|596|597|598|599|70|71|72|73|74|75|76|77|78|79
or like this (if you'd prefer a more efficient evaluation of the expression):
1([035]|1[134578]|6[124-8]|7[24]|8[0-467])|2([0346]|2[2346-9]|5[125]|9[479])|3([03568]|1[34-8]|2[01]|4[1-8])|4([0356]|1[12368]|7[58]|8[15-8]|9[23579])|5([0358]|[19][1-9]|2[1-5789]|4[13-8]|6[126]|7[0-3578])|7[0-9]
I have used the nuget package libphonenumber-csharp.
That has helped me to create a (Dutch) phone number validator, here is a code snippet, without other parts of my solution it will not compile but at least you can get an idea of how to handle this.
public override void Validate()
{
ValidationMessages = new Dictionary<string, string>();
ErrorMessage = string.Empty;
string phoneNumber;
string countryCode = _defaultCountryCode;
// If the phoneNumber is not required, it is allowed to be empty.
// So in that case isValid gets defaultvalue true
bool isValid = (!_isRequired);
if (!string.IsNullOrEmpty(_phoneNumber))
{
var phoneUtil = PhoneNumberUtil.GetInstance();
try
{
phoneNumber = PhoneNumbers.PhoneNumberUtil.Normalize(_phoneNumber);
countryCode = PhoneNumberUtil2.GetRegionCode(phoneNumber, _defaultCountryCode);
PhoneNumber oPhoneNumber = phoneUtil.Parse(phoneNumber, countryCode);
var t1 = oPhoneNumber.NationalNumber;
var t2 = oPhoneNumber.CountryCode;
var formattedNo = phoneUtil.Format(oPhoneNumber, PhoneNumberFormat.E164);
isValid = PhoneNumbers.PhoneNumberUtil.IsViablePhoneNumber(formattedNo);
}
catch (NumberParseException e)
{
var err = e.ToString();
isValid = false;
}
}
if ((isValid) && (!string.IsNullOrEmpty(_phoneNumber)))
{
Regex regexValidator = null;
string regex;
// Additional validations for Dutch phone numbers as LibPhoneNumber is to graceful as it comes to
// thinking if a number is valid.
switch (countryCode)
{
case "NL":
if (_phoneNumber.StartsWith("0800") || _phoneNumber.StartsWith("0900"))
{
// 0800/0900 numbers
regex = #"((0800|0900)(-| )?[0-9]{4}([0-9]{3})?$)";
regexValidator = new Regex(regex);
isValid = regexValidator.IsMatch(_phoneNumber);
}
else
{
string phoneNumberCheck = _phoneNumber.Replace("(", "").Replace(")", "").Replace("-", "").Replace(" ", "");
regex = #"^(0031|\+31|0)[1-9][0-9]{8}$";
regexValidator = new Regex(regex);
isValid = regexValidator.IsMatch(phoneNumberCheck);
}
break;
}
}
if (!isValid)
{
ErrorMessage = string.Format(TextProvider.Get(TextProviderConstants.ValMsg_IsInAnIncorrectFormat_0),
ColumnInfoProvider.GetLabel(_labelKey));
ValidationMessages.Add(_messageKey, ErrorMessage);
}
}
Also useful might be my class PhoneNumberUtil2 that builds upon the nuget package libphonenumber-csharp:
// Code start
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using PhoneNumbers;
namespace ProjectName.Logic.Miscellaneous
{
public class PhoneNumberUtil2
{
/// <summary>
/// Returns the alphanumeric country code for a normalized phonenumber. If a phonenumber does not contain
/// an international numeric country code, the default country code for the website is returned.
/// This works for 17 countries: NL, GB, FR, DE, BE, AU, SE, NO, IT, TK, RU, CH, DK, IR, PT, ES, FI
/// </summary>
/// <param name="normalizedPhoneNumber"></param>
/// <param name="defaultCountryCode"> </param>
/// <returns></returns>
public static string GetRegionCode(string normalizedPhoneNumber, string defaultCountryCode)
{
if (normalizedPhoneNumber.Length > 10)
{
var dict = new Dictionary<string, string>();
dict.Add("7", "RU");
dict.Add("43", "AT");
dict.Add("32", "BE");
dict.Add("45", "DK");
dict.Add("33", "FR");
dict.Add("49", "DE");
dict.Add("39", "IT");
dict.Add("31", "NL");
dict.Add("47", "NO");
dict.Add("34", "ES");
dict.Add("46", "SE");
dict.Add("41", "CH");
dict.Add("90", "TR");
dict.Add("44", "GB");
dict.Add("351", "PT");
dict.Add("353", "IE");
dict.Add("358", "FI");
// First check 3-digits International Calling Codes
if (dict.ContainsKey(normalizedPhoneNumber.Substring(0, 3)))
{
return dict[normalizedPhoneNumber.Substring(0, 3)];
}
// Then 2-digits International Calling Codes
if (dict.ContainsKey(normalizedPhoneNumber.Substring(0, 2)))
{
return dict[normalizedPhoneNumber.Substring(0, 2)];
}
// And finally 1-digit International Calling Codes
if (dict.ContainsKey(normalizedPhoneNumber.Substring(0, 1)))
{
return dict[normalizedPhoneNumber.Substring(0, 1)];
}
}
return defaultCountryCode;
}
}
}

How to properly store and lookup areacode from a phone number

How do I store areacodes (npa-nxx) in a database for fast lookup?
Here's the deal: I have a variable that contains a phone number and I need to look in a database for the city attached to that phone number.
The problem is, different countries have different formats.
Canada/USA: +19055551234 (+1 > Country, 905 > Area Code, 555 > City Code)
France: +33512345678 (+33 > Country, 5 > Areacode, 1 > City, Other numbers > Subscriber number)
and so on (infos based on wikipedia)
I created a table called 'npanxx' that contain the list of area codes, city code and city attached to each one (with the id of the country and the province/state id):
CountryId, RegionId, PrimaryCity, npa, nxx, fnpanxx
1 11 Acton Vale 450 236 +1450236
I am thinking about the following procedure:
Get all country codes from sql to php array
Go through each entry and check if there's a match from the beginning of the phone number
When (If there's) a match is found
Remove the beginning of the phone number
Get all npa-nxx that belong to that contry and put them in a php array
Go through each value of the array to find a matching beginning
When (If there's) a match is found
Remove the beginning of the phone number
Store data in different variables like: $country = 'Canada'; $city = 'Acton Vale'...
etc, etc.
First mistake (I think): To much database requests (the npanxx table contain 3000 records for only one province in Canada)
Second mistake: I'm pretty sure there's no need to go through each and every npa-nxx code
another problem: It's not sure that if the phone number is a France one that this procedure will work.
And... If there's an entry for, let's say 336 and another for 3364, it might give the wrong result.
Do you have any idea how I can solve this problem ? (I don't ask for any code, I don't want to to do the work for me, I would like some clues though)
This is for a personnel project to make donation for Multiple Sclerosis Society of Canada and would really like to finish that project :)
I would think maybe some kind of set of reg-exes or other pattern matches to whiddle down your options in terms of search. Just some basic way or "guessing" at the possibilities instead of searching all of them.
Here's a small script I wrote in PHP to return the NPA/NXX as a JSON object in real time from area-codes.com.
It returns some very useful data. It's only for the NANP, so it doesn't do so well trying to discern international calls. For that, I would suggest making a table of all international country codes and the appropriate methods to dial them, internationally.
Additionally, network exchange operators demand an international dial code (like 011 for the USA, or + for cell phones, in general) to figure out if the number is international, and then take the steps, above, to figure out where you're trying to go. You could add this constraint into the input field and be done with it.
If you're trying to just get NPA/NXX information in the North American Numbering Plan, though, this script should be very helpful.
Just an aside, the area-codes.com counts online lookups among their free services, and I have found nothing on the site to suggest that this code violates that policy. But this code can be retooled to gather data from other providers, none-the-less.
<?php
// Small script to return and format all data from the NPA/NXX info site www.area-codes.com
// Returns a JSON object.
error_reporting(E_NONE);
$npa = $_GET['npa'];
$nxx = $_GET['nxx'];
function parseInput($input) {
$v = new DOMDocument();
$v->formatOutput = true;
$v->preserveWhiteSpace = false;
$v->loadHTML($input);
$list = $v->getElementsByTagName("td");
$e = false;
$dataOut = array();
$p = "";
foreach($list as $objNode) {
if (!$e) {
$p = $objNode->nodeValue;
$p = strtolower($p);
$p = preg_replace("%[+ .:()\/_-]%", "", $p);
$p = str_replace("\xc2\xa0", "", $p);
$p = trim($p);
}
else {
if ($p != "") {
$d = trim($objNode->nodeValue);
if ($d != "") $dataOut[$p] = $d;
}
$p = "";
}
$e = !$e;
}
return $dataOut;
}
function getNPANXX($npa, $nxx) {
$url = "www.area-codes.com/exchange/exchange.asp?npa=$npa&nxx=$nxx";
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_VERBOSE, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible;)");
curl_setopt($ch, CURLOPT_URL, $url);
$response = curl_exec($ch);
curl_close($ch);
$i = strpos($response, "<h3>AreaCode/Prefix $npa-$nxx Details</h3>");
$i = strpos($response, "<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">", $i);
$e = strpos($response, "</table>", $i);
$scan = substr($response, $i, ($e-$i) + 8);
return parseInput($scan);
}
$result = getNPANXX($npa, $nxx);
if (!isset($result['npaareacode'])) {
$result = array("error" => "invalid");
}
echo json_encode($result);
die;
?>
For the query npanxx.php?npa=202&nxx=520 the JSON outputs as follows:
{
"npaareacode":"202",
"nxxusetype":"WIRELESS",
"nxxprefix":"520",
"nxxintroversion":"11\/16\/2007",
"city":"WASHINGTON",
"state":"DC",
"latitude":"38.901",
"county":"DISTRICT OF COLUMBIA",
"longitude":"-77.0315",
"countypopulation":"0",
"lata":"236",
"zipcode":"20005",
"zipcodecount":"0",
"ratecenter":"WSHNGTNZN1",
"zipcodefreq":"0",
"fips":"11001",
"ocn":"6664",
"observesdst":"Unknown",
"cbsacode":"47900",
"timezone":"Eastern (GMT -05:00)",
"cbsaname":"Washington-Arlington-Alexandria, DC-VA-MD-WV",
"carriercompany":"SPRINT SPECTRUM L.P."
}
For your example npanxx.php?npa=450&nxx=236 the data returned is a little bit limited because it's Canada and Canada doesn't provide all the FIPS and carrier data like the United States does, but the returned data still quite useful:
{
"npaareacode":"450",
"nxxusetype":"WIRELESS",
"nxxprefix":"236",
"nxxintroversion":"2002-08-04",
"city":"ACTON VALE",
"state":"QC",
"latitude":"45.6523",
"longitude":"-72.5671",
"countypopulation":"51400",
"lata":"850",
"zipcodecount":"0",
"zipcodefreq":"-1",
"observesdst":"Unknown",
"timezone":"Eastern (GMT -05:00)"
}

Finding Closest Postcode of a Given Post Code

In my php application I want to get the nearest postal code of the given post code.
That means I enter a post code as 680721 I want to get the nearest post code of this from my database.
How can I do this?
This is the table I used for store postal codes.
Here varpin is the postal code field.
Having said all this, a quick browse through the “External Links” on the UK Postcodes Wikipedia entry, and I quickly found an article by Paul Jenkins entitled UK Post Code Distance Calculation in PHP, which is fantastic, you can even download it here (uk_postcode_calc.zip).
After a short examination it seems this does exactly what it says on the tin, and simply calculates the distance.
However, with a quick google for php distance calculation, you can quickly find that there are more refined equivalents of the distance calculation. I thought it might be a good idea to use one of those instead.
After a bit of tweaking, here’s what I came up with in the end:
function distance($lat1, $lon1, $lat2, $lon2, $u=’1′) {
$u=strtolower($u);
if ($u == ‘k’) { $u=1.609344; } // kilometers
elseif ($u == ‘n’) { $u=0.8684; } // nautical miles
elseif ($u == ‘m’) { $u=1; } // statute miles (default)
$d=sin(deg2rad($lat1))*sin(deg2rad($lat2))+cos(deg2rad($lat1))*cos(deg2rad($lat2))*cos(deg2rad($lon1-$lon2));
$d=rad2deg(acos($d));
$d=$d*60*1.1515;
$d=($d*$u); // apply unit
$d=round($d); // optional
return $d;
}
So, that’s the hard parts done (database and maths), next is simply a case of using this information to “find the closest” from the postcode we input to an array of postcodes we supply…
To find the “closest” postcode, effectively what we’re trying to do is find the “shortest” distance between the postcodes, or, simply the smallest number in the results, assuming we put the results into an array with the key as the postcode and the distance as the value.
All we have to do is create a simple script that will find the smallest number in a given array, then return the appropriate key. Simple!
function closest ($needle,$haystack) {
if (!$needle || !$haystack) { return; }
if (!is_array($haystack)) { return; }
$smallest=min($haystack); //smallest value
foreach ($haystack as $key => $val) {
if ($val == $smallest) { return $key; }
}
}
The above script does exactly what we want, using the “min” function we can quickly work out what we need to return.
The only task left is to bind all this together, we need to create two functions that will:
Get the distance using the postcode to get the longitude and latitude from the database.
Create an array with the postcodes as the keys, and the distance as the values.
Very simple!
Function 1, Postcode Distance
function postcode_distance ($from,$to) {
// Settings for if you have a different database structure
$table=’postcodes_uk’;
$lat=’lat’;
$lon=’lon’;
$postcode=’postcode’;
// This is a check to ensure we have a database connection
if (!#mysql_query(‘SELECT 0′)) { return; }
// Simple regex to grab the first part of the postcode
preg_match(‘/[A-Z]{1,2}[0-9R][0-9A-Z]?/’,strtoupper($from),$match);
$one=$match[0];
preg_match(‘/[A-Z]{1,2}[0-9R][0-9A-Z]?/’,strtoupper($to),$match);
$two=$match[0];
$sql = “SELECT `$lat`, `$lon` FROM `$table` WHERE `$postcode`=’$one’”;
$query = mysql_query($sql);
$one = mysql_fetch_row($query);
$sql = “SELECT `$lat`, `$lon` FROM `$table` WHERE `$postcode`=’$two’”;
$query = mysql_query($sql);
$two = mysql_fetch_row($query);
$distance = distance($one[0], $one[1], $two[0], $two[1]);
// For debug only…
//echo “The distance between postcode: $from and postcode: $to is $distance miles\n”;
return $distance;
}
Function 2, Postcode Closest
function postcode_closest ($needle,$haystack) {
if (!$needle || !$haystack) { return; }
if (!is_array($haystack)) { return; }
foreach ($haystack as $postcode) {
$results[$postcode]=postcode_distance($needle,$postcode);
}
return closest($needle,$results);
}
So, with that done, place the 4 above functions into a file such as “postcode.php”, ready for use in the real world…
Test case:
<?php
include_once(‘postcode.php’);
if ($_POST) {
include_once(‘db.php’);
$postcodes=array(‘TF9 9BA’,'ST4 3NP’);
$input=strtoupper($_POST['postcode']);
$closest=postcode_closest($input,$postcodes);
}
if (isset($closest)) {
echo “The closest postcode is: $closest”;
}
?>
<form action=”" method=”post”>
Postcode: <input name=”postcode” maxlength=”9″ /><br />
<input type=”submit” />
</form>
You can download this script here: postcode_search.phps
Note: In the above test case, I have a “db.php” file which contains my database details and starts a database connection. I suggest you do the same.
Ensure you have your database populated, you should be able to use Paul Jenkins’s UK Postcode csv, allowing you to use your own table structure.
Well, that’s all folks, I can now use this script to provide any locations that match the “closest” postcode.

Calculate circumference values

I have a rectangular map, stored as multidimensional array (ie $map[row][col]) and I have to track down which squares are seen by a player, placed anywhere on this map.
Player visibility is circular with unknown radius (but given at run-time) and I only need integer solutions.
I know that circumference formula is
x^2 + y^2 <= r^2
but how can I store everything?
I need these values since then I can "reveal" map squares.
The best would be a multidimesional array (ie __$sol[x][y]__).
This is a piece of code that I'm using. It's not corrected since it assumes that vision is a square and not a circle.
Calculating the square
$this->vision_offsets_2 = array();
//visibility given as r^2
$mx = (int)(sqrt($this->viewradius2));
$mxArr = range($mx * -1, $mx + 1);
foreach ($mxArr as $d_row)
{
foreach ($mxArr as $d_col)
{
$this->vision_offsets_2[] = array($d_row, $d_col);
}
}
This is how I apply that
foreach($player as $bot)
{
foreach($visibility as $offset)
{
$vision_row = $offset[0] + $bot[0];
$vision_col = $offset[1] + $bot[1];
if(isset($map[$vision_row][$vision_col]))
{
if( $map[$vision_row][$vision_col] == UNSEEN) {
$map[$vision_row][$vision_col] = LAND; }
}
}
}
Here you can find the bot view: as you can see is a non perfect circle.
How can I track it? By the way, in this example radius^2 is 55, the orange circle is the player, brown squares are visible ones.
Structure
You're already referencing terrain in a grid. Store terrain objects in those grid values. Apply attributes to those objects. Check with something like
$map[$x][$y]->isVisible($player);
You'll need some methods in there for setting vision and tests for checking the user that is passed against a list of users who can see it. While you're at it, setup other related methods in those objects (I see references to land... isLand() and isWater() perhaps?).
You can even have vision cascade within objects such that you only need to move the position of a user and the object takes care of triggering off all the code to set nearby plots of land to visible.
Math
We are given circumference.
double diameter = circumference / 3.14159
double radius = diameter / 2 //Normally done in one step / variable
Now we must know the distance between two points to compare it. Let's use map[4][7] and map[3][9].
int x0 = 4;
int y0 = 7;
int x1 = 3;
int y1 = 9;
double distance = Math.sqrt(
Math.pow(x0 - x1, 2) +
Math.pow(y0 - y1, 2)
);
System.out.println(distance); //2.23606797749979
Test distance > radius.
Testing each square
Put the above in a method: visibleFrom(Square target)
radius should be a globally accessible static variable when comparing.
Your Square object should be able to hand over its coordinates.
target.getX()
target.getY()
Some optimizations can be had
Only checking things for circular distance when they're in the square.
Not checking anything for circular distance when purely along the x or y axis.
Figuring out the largest square that fits inside the circle and not checking boxes in that range for circular distance.
Remember that premature optimization and over optimization are pitfalls.
A function like this would tell you if a map square is visible (using the distance of the centers of the squares as a metric; if you want to define visibility in another manner, which you probably would, things get much more complicated):
function is_visible($mapX, $mapX, $playerX, $playerY, $r) {
return sqrt(pow($mapX - $playerX, 2) + pow($mapY - $playerY, 2)) <= $r;
}
You probably don't really need to store these values since you can easily calculate them on demand.
I think that Bresenham's circle drawing algorithm is what you're looking for.
I don't know exactly what you want, but here's some things that should help you along. As a warning these are untested, but the logic is sound.
//You mentioned circumference, this will find out the circumference but I don't
//think you actually need it.
$circumference_length = 2 * $visibility_range * 3.1415;
//Plug in the player and target coordinates and how far you can see, this will
//tell you if the player can see it. This can be optimized using your object
//and player Objects.
function canSee($player_x, $player_y, $vision_length, $target_x, $target_y){
$difference_x = $target_x - $player_x;
$difference_y = $target_y - $player_y;
$distance = sqrt((pow($difference_x,2) + pow($difference_y, 2));
if($vision < $distance){
return false;
} else {
return true;
}
}
Edit: In response to your clarification, you can use the above function to figure out if you should show the terrain objects or not.
foreach($player as $bot)
{
foreach($terrain_thing as $terrain)
{
//ASSUMING THAT [0] IS ALWAYS X AND [1] IS ALWAYS y, set a third variable
//to indicate visibility
$terrain["is_visible"] = canSee($bot[0], $bot[1], $visibility_range, $terrain[0], $terrain[1])
}
}

Categories