/**
* Given a number on a certain base until 10, calculates and return the equivalent number on another
* base until 10.
*
* @param origin_number the number to be converted.
* @param origin_base the base where `origin_number` is on.
* @param destiny_base the base where `origin_number` is to be converted to.
*/stock convert_numeric_base( origin_number, origin_base, destiny_base ){new integer;
new Array:digits;
digits = toDigitsRepresentation( origin_number, 10);
integer = fromDigitsRepresentation( digits, origin_base );
ArrayDestroy( digits );
digits = toDigitsRepresentation( integer, destiny_base );
integer = fromDigitsRepresentation( digits, 10);
ArrayDestroy( digits );
return integer;
}/**
* Read an digits Dynamic Array at its given base and return an integer representation.
*
* @param digits a Dynamic Array within the digits of inputed number on the specified base.
* @param origin_base the base where `digits` are represented.
*
* return an integer representation inputed number on the specified base.
*/stock fromDigitsRepresentation( Array:digits, origin_base ){new integer;
new arraySize = ArraySize( digits );
for(new index = 0; index < arraySize; index ++ ){
integer = integer * origin_base + ArrayGetCell( digits, index );
}return integer;
}/**
* Create an Dynamic Array within of the decimal number at its given base.
*
* @param origin_number a decimal number to be converted.
* @param origin_base the base where `origin_number` is to be converted to.
*
* return a Dynamic Array within the digits of inputed number on the specified base.
*/stock Array:toDigitsRepresentation( origin_number, origin_base ){new Array:digits = ArrayCreate();
ArrayPushCell( digits, origin_number % origin_base );
origin_number = origin_number / origin_base;
while( origin_number > 0){
ArrayInsertCellBefore( digits, 0, origin_number % origin_base );
origin_number = origin_number / origin_base;
}return digits;
}
The Unit Tests Results:
HTML Code:
L 12/29/2016 - 16:59:05: {1.000 16292 -1944939 -1944939} I AM ENTERING ON configureTheUnitTests(0)
L 12/29/2016 - 16:59:05: {1.000 16296 -1944936 3}
L 12/29/2016 - 16:59:05: {1.000 16296 -1944919 17}
L 12/29/2016 - 16:59:05: {1.000 16296 -1944914 5}
L 12/29/2016 - 16:59:05: {1.000 16296 -1944910 4}
L 12/29/2016 - 16:59:05: {1.000 16296 -1944904 6}
L 12/29/2016 - 16:59:05: {1.000 14928 -1944899 5} EXECUTING TEST 1 AFTER 1 SECONDS - test_convertNumericBase.aa_case1
L 12/29/2016 - 16:59:05: {1.000 15196 -1944889 10} I AM ENTERING ON convert_numeric_base(3) | number: 10 (7->10)
L 12/29/2016 - 16:59:05: {1.000 14692 -1944886 3} Array Cells: (0) 1, (1) 0,
L 12/29/2016 - 16:59:05: {1.000 14692 -1944882 4} Array Cells: (0) 7,
L 12/29/2016 - 16:59:05: {1.000 15196 -1944878 4} ( convert_numeric_base ) Returning integer: 7
L 12/29/2016 - 16:59:05: {1.000 14904 -1944872 6} ( displaysLastTestOk ) numberOfFailures: 0, lastFailure: 0, lastTestId: 1
L 12/29/2016 - 16:59:05: {1.000 14920 -1944868 4} OK!
L 12/29/2016 - 16:59:05: {1.000 14920 -1944863 5}
L 12/29/2016 - 16:59:05: {1.000 14920 -1944859 4}
L 12/29/2016 - 16:59:05: {1.000 14928 -1944853 6} EXECUTING TEST 2 AFTER 1 SECONDS - test_convertNumericBase.aa_case2
L 12/29/2016 - 16:59:05: {1.000 15196 -1944849 4} I AM ENTERING ON convert_numeric_base(3) | number: 10 (6->10)
L 12/29/2016 - 16:59:05: {1.000 14692 -1944845 4} Array Cells: (0) 1, (1) 0,
L 12/29/2016 - 16:59:05: {1.000 14692 -1944841 4} Array Cells: (0) 6,
L 12/29/2016 - 16:59:05: {1.000 15196 -1944836 5} ( convert_numeric_base ) Returning integer: 6
L 12/29/2016 - 16:59:05: {1.000 14904 -1944832 4} ( displaysLastTestOk ) numberOfFailures: 0, lastFailure: 0, lastTestId: 2
L 12/29/2016 - 16:59:05: {1.000 14920 -1944828 4} OK!
L 12/29/2016 - 16:59:05: {1.000 14920 -1944822 6}
L 12/29/2016 - 16:59:05: {1.000 14920 -1944816 6}
L 12/29/2016 - 16:59:05: {1.000 14928 -1944812 4} EXECUTING TEST 3 AFTER 1 SECONDS - test_convertNumericBase.aa_case3
L 12/29/2016 - 16:59:05: {1.000 15196 -1944808 4} I AM ENTERING ON convert_numeric_base(3) | number: 10 (5->10)
L 12/29/2016 - 16:59:05: {1.000 14692 -1944803 5} Array Cells: (0) 1, (1) 0,
L 12/29/2016 - 16:59:06: {1.000 14692 -1944798 5} Array Cells: (0) 5,
L 12/29/2016 - 16:59:06: {1.000 15196 -1944794 4} ( convert_numeric_base ) Returning integer: 5
L 12/29/2016 - 16:59:06: {1.000 14904 -1944790 4} ( displaysLastTestOk ) numberOfFailures: 0, lastFailure: 0, lastTestId: 3
L 12/29/2016 - 16:59:06: {1.000 14920 -1944784 6} OK!
L 12/29/2016 - 16:59:06: {1.000 14920 -1944779 5}
L 12/29/2016 - 16:59:06: {1.000 14920 -1944776 3}
L 12/29/2016 - 16:59:06: {1.000 14928 -1944769 7} EXECUTING TEST 4 AFTER 2 SECONDS - test_convertNumericBase.aa_case4
L 12/29/2016 - 16:59:06: {1.000 15196 -1944765 4} I AM ENTERING ON convert_numeric_base(3) | number: 10 (8->10)
L 12/29/2016 - 16:59:06: {1.000 14692 -1944761 4} Array Cells: (0) 1, (1) 0,
L 12/29/2016 - 16:59:06: {1.000 14692 -1944758 3} Array Cells: (0) 8,
L 12/29/2016 - 16:59:06: {1.000 15196 -1944751 7} ( convert_numeric_base ) Returning integer: 8
L 12/29/2016 - 16:59:06: {1.000 14904 -1944748 3} ( displaysLastTestOk ) numberOfFailures: 0, lastFailure: 0, lastTestId: 4
L 12/29/2016 - 16:59:06: {1.000 14920 -1944744 4} OK!
L 12/29/2016 - 16:59:06: {1.000 14920 -1944736 8}
L 12/29/2016 - 16:59:06: {1.000 14920 -1944732 4}
L 12/29/2016 - 16:59:06: {1.000 14928 -1944730 2} EXECUTING TEST 5 AFTER 2 SECONDS - test_convertNumericBase.aa_case5
L 12/29/2016 - 16:59:06: {1.000 15196 -1944726 4} I AM ENTERING ON convert_numeric_base(3) | number: 10 (9->10)
L 12/29/2016 - 16:59:06: {1.000 14692 -1944719 7} Array Cells: (0) 1, (1) 0,
L 12/29/2016 - 16:59:06: {1.000 14692 -1944716 3} Array Cells: (0) 9,
L 12/29/2016 - 16:59:06: {1.000 15196 -1944712 4} ( convert_numeric_base ) Returning integer: 9
L 12/29/2016 - 16:59:06: {1.000 14904 -1944708 4} ( displaysLastTestOk ) numberOfFailures: 0, lastFailure: 0, lastTestId: 5
L 12/29/2016 - 16:59:06: {1.000 14920 -1944702 6} OK!
L 12/29/2016 - 16:59:06: {1.000 14920 -1944698 4}
L 12/29/2016 - 16:59:06: {1.000 14920 -1944694 4}
L 12/29/2016 - 16:59:06: {1.000 14928 -1944689 5} EXECUTING TEST 6 AFTER 2 SECONDS - test_convertNumericBase.aa_case6
L 12/29/2016 - 16:59:06: {1.000 15196 -1944684 5} I AM ENTERING ON convert_numeric_base(3) | number: 10 (10->10)
L 12/29/2016 - 16:59:06: {1.000 14692 -1944681 3} Array Cells: (0) 1, (1) 0,
L 12/29/2016 - 16:59:06: {1.000 14692 -1944677 4} Array Cells: (0) 1, (1) 0,
L 12/29/2016 - 16:59:06: {1.000 15196 -1944671 6} ( convert_numeric_base ) Returning integer: 10
L 12/29/2016 - 16:59:06: {1.000 14904 -1944667 4} ( displaysLastTestOk ) numberOfFailures: 0, lastFailure: 0, lastTestId: 6
L 12/29/2016 - 16:59:06: {1.000 14920 -1944663 4} OK!
L 12/29/2016 - 16:59:06: {1.000 14920 -1944659 4}
L 12/29/2016 - 16:59:06: {1.000 14920 -1944653 6}
L 12/29/2016 - 16:59:06: {1.000 14928 -1944649 4} EXECUTING TEST 7 AFTER 2 SECONDS - test_convertNumericBase.aa_case7
L 12/29/2016 - 16:59:06: {1.000 15196 -1944645 4} I AM ENTERING ON convert_numeric_base(3) | number: 11 (7->9)
L 12/29/2016 - 16:59:06: {1.000 14692 -1944641 4} Array Cells: (0) 1, (1) 1,
L 12/29/2016 - 16:59:06: {1.000 14692 -1944636 5} Array Cells: (0) 8,
L 12/29/2016 - 16:59:06: {1.000 15196 -1944632 4} ( convert_numeric_base ) Returning integer: 8
L 12/29/2016 - 16:59:06: {1.000 14904 -1944628 4} ( displaysLastTestOk ) numberOfFailures: 0, lastFailure: 0, lastTestId: 7
L 12/29/2016 - 16:59:06: {1.000 14920 -1944622 6} OK!
L 12/29/2016 - 16:59:06: {1.000 14920 -1944618 4}
L 12/29/2016 - 16:59:06: {1.000 14920 -1944614 4}
L 12/29/2016 - 16:59:06: {1.000 14928 -1944610 4} EXECUTING TEST 8 AFTER 2 SECONDS - test_convertNumericBase.aa_case8
L 12/29/2016 - 16:59:06: {1.000 15196 -1944608 2} I AM ENTERING ON convert_numeric_base(3) | number: 2462 (7->9)
L 12/29/2016 - 16:59:06: {1.000 14668 -1944601 7} Array Cells: (0) 2, (1) 4, (2) 6, (3) 2,
L 12/29/2016 - 16:59:06: {1.000 14668 -1944597 4} Array Cells: (0) 1, (1) 2, (2) 3, (3) 8,
L 12/29/2016 - 16:59:06: {1.000 15196 -1944593 4} ( convert_numeric_base ) Returning integer: 1238
L 12/29/2016 - 16:59:06: {1.000 14904 -1944586 7} ( displaysLastTestOk ) numberOfFailures: 0, lastFailure: 0, lastTestId: 8
L 12/29/2016 - 16:59:06: {1.000 14920 -1944583 3} OK!
L 12/29/2016 - 16:59:06: {1.000 14920 -1944579 4}
L 12/29/2016 - 16:59:06: {1.000 14920 -1944575 4}
L 12/29/2016 - 16:59:06: {1.000 14928 -1944569 6} EXECUTING TEST 9 AFTER 2 SECONDS - test_convertNumericBase.aa_case9
L 12/29/2016 - 16:59:06: {1.000 15196 -1944565 4} I AM ENTERING ON convert_numeric_base(3) | number: 1238 (9->7)
L 12/29/2016 - 16:59:06: {1.000 14668 -1944561 4} Array Cells: (0) 1, (1) 2, (2) 3, (3) 8,
L 12/29/2016 - 16:59:06: {1.000 14668 -1944556 5} Array Cells: (0) 2, (1) 4, (2) 6, (3) 2,
L 12/29/2016 - 16:59:06: {1.000 15196 -1944551 5} ( convert_numeric_base ) Returning integer: 2462
L 12/29/2016 - 16:59:06: {1.000 16256 -1944548 3} ( displaysLastTestOk ) numberOfFailures: 0, lastFailure: 0, lastTestId: 9
L 12/29/2016 - 16:59:06: {1.000 16272 -1944544 4} OK!
L 12/29/2016 - 16:59:06: {1.000 16272 -1944538 6}
L 12/29/2016 - 16:59:06: {1.000 16272 -1944534 4}
L 12/29/2016 - 16:59:06: {1.000 16296 -1944530 4}
L 12/29/2016 - 16:59:06: {1.000 16280 -1944526 4} I AM ENTERING ON print_all_tests_executed(0)
L 12/29/2016 - 16:59:06: {1.000 16024 -1944520 6}
L 12/29/2016 - 16:59:06: {1.000 16024 -1944517 3}
L 12/29/2016 - 16:59:06: {1.000 16024 -1944512 5}
L 12/29/2016 - 16:59:06: {1.000 16024 -1944509 3} The following tests were executed:
L 12/29/2016 - 16:59:06: {1.000 16024 -1944503 6}
L 12/29/2016 - 16:59:06: {1.000 16008 -1944498 5} 1. test_convertNumericBase.aa_case1
L 12/29/2016 - 16:59:06: {1.000 16008 -1944494 4} 2. test_convertNumericBase.aa_case2
L 12/29/2016 - 16:59:06: {1.000 16008 -1944489 5} 3. test_convertNumericBase.aa_case3
L 12/29/2016 - 16:59:06: {1.000 16008 -1944485 4} 4. test_convertNumericBase.aa_case4
L 12/29/2016 - 16:59:06: {1.000 16008 -1944484 1} 5. test_convertNumericBase.aa_case5
L 12/29/2016 - 16:59:06: {1.000 16008 -1944476 8} 6. test_convertNumericBase.aa_case6
L 12/29/2016 - 16:59:06: {1.000 16008 -1944470 6} 7. test_convertNumericBase.aa_case7
L 12/29/2016 - 16:59:06: {1.000 16008 -1944466 4} 8. test_convertNumericBase.aa_case8
L 12/29/2016 - 16:59:06: {1.000 16008 -1944462 4} 9. test_convertNumericBase.aa_case9
L 12/29/2016 - 16:59:06: {1.000 14996 -1944458 4}
L 12/29/2016 - 16:59:06: {1.000 14988 -1944453 5} 9 tests succeed.
L 12/29/2016 - 16:59:06: {1.000 14992 -1944449 4} 0 tests failed.
L 12/29/2016 - 16:59:06: {1.000 16284 -1944445 4}
L 12/29/2016 - 16:59:06: {1.000 16272 -1944439 6} Finished the General Numeric Base Conversion's Unit Tests execution after '2' seconds.
L 12/29/2016 - 16:59:06: {1.000 16284 -1944435 4}
L 12/29/2016 - 16:59:06: {1.000 16284 -1944431 4}
The Unit Tests Source Code:
Spoiler
PHP Code:
/** AMX Mod X Script * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or ( at * your option ) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * ***********************************************************
/** * Call the internal function to perform its task and stop the current test execution to avoid * double failure at the test control system. * * @see the stock 'setTestFailure(3)'. */ #define SET_TEST_FAILURE(%1) \ { \ if( setTestFailure( %1 ) ) \ { \ LOGGER( 1, " ( SET_TEST_FAILURE ) Just returning/bloking." ) \ return; \ } \ }
/** * General handler to assist object property applying and keep the code clear. This only need * to be used with destructors/cleaners which does not support uninitialized handlers, requiring * an if pre-checking. * * @param objectHandler the object handler to be called. * @param objectIndentifation the object identification number to be destroyed. */ #define TRY_TO_APPLY(%1,%2) \ { \ LOGGER( 128, "I AM ENTERING ON TRY_TO_APPLY(2) | objectIndentifation: %d", %2 ) \ if( %2 ) \ { \ %1( %2 ); \ } \ }
new g_test_testsNumber; new g_debug_level = 255; new g_test_lastTimeStamp; new g_test_startDayInteger;
/** * Used to know when the Unit Tests are running. */ new bool:g_test_isTheUnitTestsRunning;
/** * General data. */ new const PLUGIN_NAME[] = "General Numeric Base Conversion"; new const DEBUGGER_OUTPUT_LOG_FILE_NAME[] = "text.log";
/** * Test unit variables related to the DEBUG_LEVEL_UNIT_TESTs'. */ new g_test_failureNumber;
new Trie: g_test_failureIdsTrie; new Array:g_test_failureIdsArray; new Array:g_test_idsAndNamesArray; new Array:g_test_failureReasonsArray; // ################################# Unit Tests Header End ####################################
public plugin_init() { register_plugin( PLUGIN_NAME, "1.0", "Addons zz" );
/** * Create one case test for the stock convert_numeric_base(0) based on its parameters passed * by the test_convertNumericBase_load(0) loader function. */ stock test_convertNumericBase( origin_number, origin_base, destiny_base, expected ) { new errorMessage[ MAX_LONG_STRING ]; new test_id = test_registerSeriesNaming( "test_convertNumericBase", 'a' );
new result = convert_numeric_base( origin_number, origin_base, destiny_base );
formatex( errorMessage, charsmax( errorMessage ), "Converting the number %d on base %d to base %d must to be %d, instead of %d.", origin_number, origin_base, destiny_base, expected, result );
SET_TEST_FAILURE( test_id, result != expected, errorMessage ) }
/** * Given a number on a certain base until 10, calculates and return the equivalent number on another * base until 10. * * @param origin_number the number to be converted. * @param origin_base the base where `origin_number` is on. * @param destiny_base the base where `origin_number` is to be converted to. */ stock convert_numeric_base( origin_number, origin_base, destiny_base ) { LOGGER( 128, "I AM ENTERING ON convert_numeric_base(3) | number: %d (%d->%d)", \ origin_number, origin_base, destiny_base )
/** * Read an digits Dynamic Array at its given base and return an integer representation. * * @param digits a Dynamic Array within the digits of inputed number on the specified base. * @param origin_base the base where `digits` are represented. * * return an integer representation inputed number on the specified base. */ stock fromDigitsRepresentation( Array:digits, origin_base ) { new integer; new arraySize = ArraySize( digits );
for( new index = 0; index < arraySize; index ++ ) { integer = integer * origin_base + ArrayGetCell( digits, index ); }
return integer; }
/** * Create an Dynamic Array within of the decimal number at its given base. * * @param origin_number a decimal number to be converted. * @param origin_base the base where `origin_number` is to be converted to. * * return a Dynamic Array within the digits of inputed number on the specified base. */ stock Array:toDigitsRepresentation( origin_number, origin_base ) { new Array:digits = ArrayCreate();
new indexString [ 8 ]; new elementString [ 8 ]; new cellString [ MAX_CELL_LENGHT ]; new cellStringsBuffer[ MAX_MESSAGE_LENGHT ];
new arraySize = ArraySize( array );
for( new index = 0; index < arraySize; index++ ) { // Create a cellString within a trailing comma formatex( indexString , charsmax( indexString ) , "(%d) " , index ); formatex( elementString, charsmax( elementString ), "%d, " , ArrayGetCell( array, index ) ); formatex( cellString , charsmax( cellString ) , "%7s %-9s" , indexString, elementString );
// Add a new cellString to the output buffer and flushes it when it full announceCellString( cellString, cellStringsBuffer ); }
// Flush the last cells flushCellStrings( cellStringsBuffer ); // server_print( "" ); }
/** * Announce cells to the server console. * * @param cellStringAnnounce a cell as string. * @param cellStringsBuffer the output string to be printed. * * @note It does not immediately print the cell, the output occurs when the buffer is full. */ stock announceCellString( cellStringAnnounce[], cellStringsBuffer[] ) { static copiedChars;
// Reset the characters counter for the output flush if( !cellStringsBuffer[ 0 ] ) { copiedChars = 0; }
// Add the cellString to the buffer copiedChars += copy( cellStringsBuffer[ copiedChars ], MAX_MESSAGE_LENGHT - 1 - copiedChars, cellStringAnnounce );
// Calculate whether to flush now or not if( copiedChars > MAX_MESSAGE_LENGHT - MAX_CELL_LENGHT ) { flushCellStrings( cellStringsBuffer ); } }
/** * Print the current buffer, if there are any cellStrings on it. * * @param cellStringsBuffer the formatted cellStrings list to be printed. */ stock flushCellStrings( cellStringsBuffer[] ) { if( cellStringsBuffer[ 0 ] ) { // Print the message LOGGER( 1, "%-13s%s", "Array Cells: ", cellStringsBuffer[ 0 ] )
/** * Write debug messages accordantly with the 'g_debug_level' variable. * * @param mode the debug mode level, see the variable 'g_debug_level' for the levels. * @param text the debug message, if omitted its default value is "" * @param any the variable number of formatting parameters * * @see the stock writeToTheDebugFile( log_file[], formated_message[] ) for the output log * 'DEBUGGER_OUTPUT_LOG_FILE_NAME'. */ stock debugMesssageLogger( const mode, const message[] = "", any:... ) { if( mode & g_debug_level ) { static formated_message[ MAX_BIG_BOSS_STRING ]; vformat( formated_message, charsmax( formated_message ), message, 3 );
/** * Write messages to the debug log file on 'addons/amxmodx/logs'. * * @param log_file the log file name. * @param formated_message the formatted message to write down to the debug log file. */ stock writeToTheDebugFile( const log_file[], const formated_message[] ) { new currentTime; static lastRun;
// Removes the compiler warning `warning 203: symbol is never used` when only the debug // level `DEBUG_LEVEL_NORMAL` is enabled. if( g_test_isTheUnitTestsRunning ) { } }
/** * Informs the Test System that the test failed and why. * * @param test_id the test_id at the Test System * @param isFailure a boolean value setting whether the failure status is true. * @param failure_reason the reason why the test failed */ stock setTestFailure( test_id, bool:isFailure, failure_reason[] ) { LOGGER( 0, "I AM ENTERING ON setTestFailure(...) | test_id: %d, isFailure: %d, \ failure_reason: %s", test_id, isFailure, failure_reason )
/** * This is the first thing called when a test begin running. It function is to let the Test System * know that the test exists and then know how to handle it using the test_id. * * @param test_name the test name to register * * @return test_id an integer that refers it at the Test System. */ stock register_test( test_name[] ) { LOGGER( 0, "I AM ENTERING ON register_test(2) | test_name: %s", test_name )
g_test_testsNumber++; print_logger( " EXECUTING TEST %d AFTER %d SECONDS - %s ", g_test_testsNumber, computeTheTestElapsedTime(),test_name );
return g_test_testsNumber; }
/** * Register a test series naming, used to easily allow to distinguish between tests names. * Example, this: * 1. test_loadVoteChoices.aa_case1 * 2. test_loadVoteChoices.aa_case2 * 3. test_loadVoteChoices.bb.bb_case1 * 4. test_loadVoteChoices.bb.bb_case2 * * Instead of this: * 1. test_loadVoteChoices.a_case1 * 2. test_loadVoteChoices.a_case2 * 3. test_loadVoteChoices.b_case1 * 4. test_loadVoteChoices.b_case2 * * @param seriesName the current test name. * @param newSeries a char as the new test series start. The default is to use the last serie. */ stock test_registerSeriesNaming( seriesName[], newSeries = 0 ) { new currentIndex; new testName[ MAX_SHORT_STRING ];
stock print_tests_results_count() { print_logger( "" ); print_logger( " Finished the %s's Unit Tests execution after '%d' seconds.", PLUGIN_NAME, computeTheTestElapsedTime() ); print_logger( "" ); print_logger( "" );
// Clear the time-stamp. g_test_lastTimeStamp = 0; }
/** * Compute how many days are elapsed since 1st January of 2000. * * @param currentDayInteger the current day from this year (1-366). * @param currentYearInteger the current year (2016). */ #define GET_CURRENT_BASED_DAY(%1,%2) ( ( %2 - 2000 ) * 366 + %1 )
/** * Save a time-stamp when the Unit Tests started to run. */ stock saveCurrentTestsTimeStamp() { if( !g_test_lastTimeStamp ) { new hour; new minute; new second;
time( hour, minute, second );
new currentDayInteger; new currentYearInteger; new rawTimeData[ 10 ];
/** * Calculates how much time took to run the Unit Tests. For this to work, the stock * 'saveCurrentTestsTimeStamp(0)' must to be called on the beginning of the tests. * * @return seconds how much seconds are elapsed, or -1 on when the time-stamp is null. */ stock computeTheTestElapsedTime() { if( g_test_lastTimeStamp ) { new hour; new minute; new second; new delayResulted;
time( hour, minute, second );
new currentDayInteger; new currentYearInteger; new rawTimeData[ 10 ];
Let us suppose you are building a map list menu by demand, i.e.,
If there are 50000 maps on you server, you do not create a `new menu style` looping throw all the maps and build a monster menu.
Also, as the menu is as big as 50000 maps, anyone will never be able to go from the first page, to the last map page hitting the more button, as it is too big.
The solution is to allow the user open an alphabetically sorted maps menu on any page it want to, accessing only enough maps from the general array map for that page.
And for that you cannot fully use the `new style menu` because for that menu, you need to provide all its entries while building it, and here it is not the case.
The solution based `new menu style` disabling the paging and using the info[] to pass the map index, not need this.
I am presenting is just for fun, as using the `new menu style` disabling the paging and using the info[] to pass the maps index is better.
This solution is using numeric base conversion applies only for the old style menu construction using a global array to store the pages where each server player is currently on.
For that you will need to calculate how many pages there are on the menu, to show it on you menu header:
Code:
/**
* Calculate which is the number of the last menu page.
*
* @param totalMenuItems how many items there are on the menu
* @param menuItemPerPage how much items there are on each menu's page
*/#define GET_LAST_PAGE_NUMBER(%1,%2) \((( %1 + 1) / %2) \
+ (((( %1 + 1) % %2) > 0) ? 1 : 0));
#define MAX_MENU_ITEMS_PER_PAGE 7
...
new lastPageNumber = GET_LAST_PAGE_NUMBER( mapsCount, MAX_MENU_ITEMS_PER_PAGE )
...
You will need to know on what page to display the menu when the menu is open, and as we allow the user to open it on any page, we need to intercept it and to set the current page accordingly.
Code:
new g_mapMenuPages[ MAX_PLAYERS + 1];
newconst MAP_MENU_COMMAND[] = "coolmenu";
/**
* Generic say handler to determine if we need to act on what was said.
*/public cmd_say( player_id ){new thirdWord[2];
static sentence [70];
static firstWord [32];
static secondWord[32];
sentence [0] = '^0';
firstWord [0] = '^0';
secondWord[0] = '^0';
read_args( sentence, charsmax( sentence ));
remove_quotes( sentence );
parse( sentence ,
firstWord , charsmax( firstWord ),
secondWord, charsmax( secondWord ),
thirdWord , charsmax( thirdWord ));
// if the chat line has more than 2 words, we're not interested at allif( thirdWord[0] == '^0'){new userFlags = get_user_flags( player_id );
// if the chat line contains 1 word, it could be a map or a one-word command as "say coolmenu50"if( secondWord[0] == '^0'){if( userFlags & ADMIN_MAP
&& containi( firstWord, MAP_MENU_COMMAND ) > -1){// Calculate how much pages there are available.new mapsCount = ArraySize( g_loadedMapsArray );
new lastPageNumber = GET_LAST_PAGE_NUMBER( mapsCount, MAX_MENU_ITEMS_PER_PAGE )
setCorrectMenuPage( player_id, firstWord, g_mapMenuPages, lastPageNumber );
mapMenuBuilder( player_id );
return PLUGIN_HANDLED;
}}elseif( userFlags & ADMIN_MAP
&& equali( firstWord, MAP_MENU_COMMAND )){// Calculate how much pages there are available.new mapsCount = ArraySize( g_loadedMapsArray );
new lastPageNumber = GET_LAST_PAGE_NUMBER( mapsCount, MAX_MENU_ITEMS_PER_PAGE )
setCorrectMenuPage( player_id, secondWord, g_mapMenuPages, lastPageNumber );
mapMenuBuilder( player_id );
return PLUGIN_HANDLED;
}}return PLUGIN_CONTINUE;
}/**
* Remove all the text from the string, except the first digits chain, to allow to open the menu
* as `say coomenuPageNumber`. For example: `say coomenu50`.
*/stock setCorrectMenuPage( player_id, pageString[], menuPages[], pagesCount ){if(strlen( pageString ) > 1){new searchIndex;
new resultIndex;
while( pageString[ searchIndex ]
&& !isdigit( pageString[ searchIndex ])){
pageString[0] = pageString[ searchIndex ];
pageString[ searchIndex ] = '^0';
searchIndex++;
}// When the page number start with a digit, we would erase all the string if not doing this.if( searchIndex == 0){
searchIndex = 1;
}while(isdigit( pageString[ searchIndex ])){
pageString[ resultIndex ] = pageString[ searchIndex ];
pageString[ searchIndex ] = '^0';
searchIndex++;
resultIndex++;
}}if(isdigit( pageString[0])){// The pages index, start on 0new targetPage = str_to_num( pageString );
if( pagesCount > targetPage ){
menuPages[ player_id ] = targetPage - 1;
}else{
menuPages[ player_id ] = pagesCount - 1;
}}}
Now you build the menu:
Code:
/**
* Common strings sizes used around the plugin.
*/#define MAX_LONG_STRING 256#define MAX_SHORT_STRING 64#define MAX_BIG_BOSS_STRING 512#define MAX_MAPNAME_LENGHT 64new Array:g_loadedMapsArray;
new g_menuName[] = "MAPS MENU"public plugin_init(){register_plugin("Maps Menu", "1.0", "Addons zz")register_menucmd(register_menuid( g_menuName ), 1023, "player_choice")// Fill the array `g_loadedMapsArray` with all the 50000 maps sorted alphabetically
...
register_clcmd("say", "cmd_say", -1);
register_clcmd("say_team", "cmd_say", -1);
}/**
* Gather all maps that match the current page on `g_mapMenuPages`, for the `player_id`.
*/stock mapMenuBuilder( player_id ){new mapIndex;
new mapsCount;
new itemsCount;
new menuKeys;
new writePosition;
new mapName [ MAX_MAPNAME_LENGHT ];
new menu_body[ MAX_BIG_BOSS_STRING ];
mapsCount = ArraySize( g_loadedMapsArray );
// Calculate how much pages there are available.new currentPageNumber = g_mapMenuPages[ player_id ];
new lastPageNumber = GET_LAST_PAGE_NUMBER( mapsCount, MAX_MENU_ITEMS_PER_PAGE )// To create the menu
writePosition = formatex( menu_body, charsmax( menu_body ), "%s %d /%d^n^n", "Map List", currentPageNumber + 1, lastPageNumber );
// Get the initial page maps index
mapIndex = currentPageNumber * MAX_MENU_ITEMS_PER_PAGE;
for( ; mapIndex < mapsCount && itemsCount < MAX_MENU_ITEMS_PER_PAGE; mapIndex++ ){
ArrayGetString( g_loadedMapsArray, mapIndex, mapName, charsmax( mapName ));
// create valid keys ( 1 to 7 )
menuKeys |= (1 << itemsCount )
itemsCount++;
writePosition = formatex( menu_body[ writePosition ], charsmax( menu_body ) - writePosition,
"%d. %s %s^n", itemsCount, mapName, disabledReason );
}// calculates the the final page buttonsif( currentPageNumber ){if( lastPageNumber == currentPageNumber + 1){
menuKeys |= MENU_KEY_0 | MENU_KEY_8;
writePosition += formatex( menu_body[ writePosition ], sizeof( menu_body ) - writePosition, "^n8. Back.^n0. Exit")}else{
menuKeys |= MENU_KEY_0 | MENU_KEY_8 | MENU_KEY_9;
writePosition += formatex( menu_body[ writePosition ], sizeof( menu_body ) - writePosition, "^n8. Back.^n9. More...^n0. Exit")}}else{
menuKeys |= MENU_KEY_0 | MENU_KEY_9;
writePosition += formatex( menu_body[ writePosition ], sizeof( menu_body ) - writePosition, "^n9. More.^n0. Exit")}show_menu( player_id, menuKeys, menu_body, 0, g_menuName )}
Now we use the numeric base conversion from the base 7 `a.k.a., MAX_MENU_ITEMS_PER_PAGE` to the base 10. This is because the menu options are written in the septal base, however the map indexes on the array `g_loadedMapsArray` are in decimal.
Code:
public player_choice( player_id, item ){/*
* You press the key 0, you gets 9 here. ...
* So here, i made the switch back.
*/switch( item ){case9: item = 0case0: item = 1case1: item = 2case2: item = 3case3: item = 4case4: item = 5case5: item = 6case6: item = 7case7: item = 8case8: item = 9}switch( item ){case1..7:
{new mapName[ MAX_MAPNAME_LENGHT ];
// Here we need to convert a number on base 7 on a number on base 10 to find the correct map array index.new pageSeptalNumber = convert_numeric_base( g_mapMenuPages[ player_id ], 10, MAX_MENU_ITEMS_PER_PAGE );
item = convert_numeric_base( pageSeptalNumber * 10, MAX_MENU_ITEMS_PER_PAGE, 10) + item - 1;
ArrayGetString( g_loadedMapsArray, item, mapName, charsmax( mapName ));
server_print("Selected map: %s, mapIndex: %d", mapName, item );
}case8:
{// Here we to perform the back button
g_mapMenuPages[ player_id ] ? g_mapMenuPages[ player_id ]-- : 0;
}case9:
{// Here we to perform the more button
g_mapMenuPages[ player_id ]++;
}default:
{// Do nothing, i.e., exit the menureturn;
}}// Here we show the menu again.
mapMenuBuilder( player_id );
}
On the above code we need to keep the global variable `g_mapMenuPages`, and doing so, we are also able to remember the last position the user was when the re-opens the menu later, which is very nice.
With this menu you may:
Close it whatever you want to and re-open on the same page you left before.
Open whatever page you want to.
Save big server performance, as you only calculate on the pages you will use, not all the 50000 maps entries at once.