// A number is called as Armstrong number if sum of cubes of digits of number is
// equal to the number itself.
// For Example 153 is an Armstrong number because 153 = 1³+5³+3³.
#include
// Function to calculate x raised to the power y
int power(int x, unsigned int y)
{
if (y == 0)
return 1;
if (y % 2 == 0)
return power(x, y / 2) * power(x, y / 2);
return x * power(x, y / 2) * power(x, y / 2);
}
// Function to calculate order of the number
int order(int x)
{
int n = 0;
while (x)
{
n++;
x = x / 10;
}
return n;
}
// Function to check whether the given number is
// Armstrong number or not
int isArmstrong(int x)
{
// Calling order function
int n = order(x);
int temp = x, sum = 0;
while (temp)
{
int r = temp % 10;
sum += power(r, n);
temp = temp / 10;
}
// If satisfies Armstrong condition
if (sum == x)
return 1;
else
return 0;
}
//
int main()
{
int x = 153;
if (isArmstrong(x) == 1)
printf("True\n");
else
printf("False\n");
x = 1253;
if (isArmstrong(x) == 1)
printf("True\n");
else
printf("False\n");
return 0;
}
这段代码实现了一个函数isArmstrong
,用于检查给定的整数是否为阿姆斯特朗数。下面是代码的详细解释:
power
函数用于计算一个数的指定次幂。它使用了递归的方式来实现幂运算。当指数y
为0时,返回1;当指数是偶数时,将问题分解为两个相同的子问题,并将它们相乘;当指数是奇数时,先计算结果的一半,再将其与原始数进行相乘。
order
函数用于计算一个数的位数。它通过对数学性质进行迭代计算来确定给定数的位数。在循环中,每次将给定数除以10并计数,直到数变成0为止。最后返回计数值,即为数字的位数。
isArmstrong
函数用于检查一个数是否为阿姆斯特朗数。该函数首先调用order
函数获取给定数的位数,并把结果保存在变量n
中。然后,使用一个循环遍历每个位上的数字。在每次迭代中,将给定数模10得到最右边的一位数r
,然后将r
的立方加到变量sum
上。然后将给定数除以10,以便处理下一位数。最后,如果sum
等于原始数,则返回1表示该数字是阿姆斯特朗数;否则返回0表示不是。
在main
函数中,两个示例用例被传递给isArmstrong
函数进行检查。第一个示例是153,该数字满足阿姆斯特朗数的条件,因此输出结果为"True"。第二个示例是1253,该数字不满足条件,因此输出结果为"False"。
因此,这段代码通过使用递归、位数计算和阿姆斯特朗数条件的检查,实现了判断一个数是否为阿姆斯特朗数的功能。
## Cantor三分集
/**
* @file
* @brief Program to generate [Cantor ternary
* set](https://en.wikipedia.org/wiki/Cantor_set)
*/
#include
#include
#include
/** structure to define Cantor set */
typedef struct _cantor_set
{
double start; /**< start of interval */
double end; /**< end of interval */
struct _cantor_set *next; /**< pointer to next set */
} CantorSet;
/** Iterative constructor of all sets in the current level. This function
* dynamically allocates memory when creating new sets. These are freed by the
* function ::free_memory.
* @param head pointer to interval set instance to update
*/
void propagate(CantorSet *head)
{
// if input is NULL, ignore the process
if (head == NULL)
return;
CantorSet *temp = head; // local pointer to track propagation
// create new node for the new set
CantorSet *newNode = (CantorSet *)malloc(sizeof(CantorSet));
// get 1/3rd of interval
double diff = (((temp->end) - (temp->start)) / 3);
// update interval ranges
newNode->end = temp->end;
temp->end = ((temp->start) + diff);
newNode->start = (newNode->end) - diff;
// update pointer to next set in this level
newNode->next = temp->next;
// point to next set
temp->next = newNode;
// create next set
propagate(temp->next->next);
}
/** Print sets in the current range to `stdout`
* @param head pointer to first set in the current level
*/
void print(CantorSet *head)
{
CantorSet *temp = head;
while (temp != NULL) // print while a valid set is found
{
printf("\t");
printf("[%lf] -- ", temp->start);
printf("[%lf]", temp->end);
temp = temp->next;
}
printf("\n");
}
/** Clear memory allocated by ::propagate function.
* @param head pointer to first allocated instance.
*/
void free_memory(CantorSet *head)
{
if (!head)
return;
if (head->next)
free_memory(head->next);
free(head);
}
/** Main function */
int main(int argc, char const *argv[])
{
int start_num, end_num, levels;
if (argc < 2)
{
printf("Enter 3 arguments: start_num \t end_num \t levels\n");
scanf("%d %d %d", &start_num, &end_num, &levels);
}
else
{
start_num = atoi(argv[1]);
end_num = atoi(argv[2]);
levels = atoi(argv[3]);
}
if (start_num < 0 || end_num < 0 || levels < 0)
{
fprintf(stderr, "All numbers must be positive\n");
return -1;
}
CantorSet head = {.start = start_num, .end = end_num, .next = NULL};
// loop to propagate each level from top to bottom
for (int i = 0; i < levels; i++)
{
printf("Level %d\t", i);
print(&head);
propagate(&head);
printf("\n");
}
printf("Level %d\t", levels);
print(&head);
// delete all memory allocated
free_memory(head.next);
return 0;
}
这段代码用于生成Cantor三分集。下面是代码的详细解释:
首先定义了一个结构体CantorSet
,用于表示Cantor集的区间。
propagate
函数是一个递归函数,用于构造当前级别的所有集合。该函数会动态分配内存来创建新的集合,并在后续的操作中释放这些内存。在函数中,首先检查输入指针是否为空。然后,创建一个新的节点来表示新的集合,并计算当前集合的长度的1/3。接下来,更新当前集合的范围,并将新的集合插入到当前集合后面。最后,递归调用自身来创建下一个级别的集合。
print
函数用于打印当前级别的集合。该函数遍历链表中的所有集合,并按格式将每个集合的起始和结束位置打印出来。
free_memory
函数用于释放propagate
函数动态分配的内存。该函数使用递归方式释放所有的内存。
在main
函数中,首先解析命令行参数或者从标准输入中读取起始数、结束数和级别数。然后,创建一个初始的CantorSet
对象作为链表的头部。接下来,通过循环依次构造并打印每个级别的集合。最后,通过调用free_memory
函数释放所有动态分配的内存。
因此,这段代码实现了一个程序,用于生成并打印Cantor三分集的级别。每个级别的集合是由前一个级别生成的,并且该程序支持从命令行参数或者标准输入中指定起始数、结束数和级别数。
/**
* @file
* @brief Program to print the nth term of the Fibonacci series.
* @details
* Fibonacci series generally starts from 0 and 1. Every next term in
* the series is equal to the sum of the two preceding terms.
* For further info: https://en.wikipedia.org/wiki/Fibonacci_sequence
*
* @author [Luiz Carlos Aguiar C](https://github.com/IKuuhakuI)
* @author [Niranjan](https://github.com/niranjank2022)
*/
#include /// for assert()
#include /// for errno - to determine whether there is an error while using strtol()
#include /// for input, output
#include /// for exit() - to exit the program
#include /// to calculate time taken by fib()
/**
* @brief Determines the nth Fibonacci term
* @param number - n in "nth term" and it can't be negative as well as zero
* @return nth term in unsigned type
* @warning
* Only till 47th and 48th fibonacci element can be stored in `int` and
* `unsigned int` respectively (takes more than 20 seconds to print)
*/
unsigned int fib(int number)
{
// Check for negative integers
if (number <= 0)
{
fprintf(stderr, "Illegal Argument Is Passed!\n");
exit(EXIT_FAILURE);
}
// Base conditions
if (number == 1)
return 0;
if (number == 2)
return 1;
// Recursive call to the function
return fib(number - 1) + fib(number - 2);
}
/**
* @brief Get the input from the user
* @return valid argument to the fibonacci function
*/
int getInput(void)
{
int num, excess_len;
char buffer[3], *endPtr;
while (1)
{ // Repeat until a valid number is entered
printf("Please enter a valid number:");
fgets(buffer, 3, stdin); // Inputs the value from user
excess_len = 0;
if (!(buffer[0] == '\n' ||
buffer[1] == '\n' ||
buffer[2] == '\n')) {
while (getchar() != '\n') excess_len++;
}
num = strtol(buffer, &endPtr,
10); // Attempts to convert the string to integer
// Checking the input
if ( // The number is too large
(excess_len > 0 || num > 48) ||
// Characters other than digits are included in the input
(*endPtr != '\0' && *endPtr != '\n') ||
// No characters are entered
endPtr == buffer)
{
continue;
}
break;
}
printf("\nEntered digit: %d (it might take sometime)\n", num);
return num;
}
/**
* @brief self-test implementation
* @return void
*/
static void test()
{
assert(fib(5) == 3);
assert(fib(2) == 1);
assert(fib(9) == 21);
}
/**
* @brief Main function
* @return 0 on exit
*/
int main()
{
// Performing the test
test();
printf("Tests passed...\n");
// Getting n
printf(
"Enter n to find nth fibonacci element...\n"
"Note: You would be asked to enter input until valid number ( less "
"than or equal to 48 ) is entered.\n");
int number = getInput();
clock_t start, end;
start = clock();
printf("Fibonacci element %d is %u ", number, fib(number));
end = clock();
printf("in %.3f seconds.\n", ((double)(end - start)) / CLOCKS_PER_SEC );
return 0;
}
这段代码实现了一个程序,用于计算和打印斐波那契序列中第n个数字,并且支持从用户输入获取n的值。下面是代码的详细解释:
fib
函数用于计算斐波那契序列的第n个数字。该函数采用递归的方式实现。首先,检查输入参数是否为负数或零,如果是,则打印错误信息并退出程序。然后,给出了前两个基本条件:当n为1时,返回0;当n为2时,返回1。接下来,通过递归调用自身来计算第n个斐波那契数,即使用fib(n-1) + fib(n-2)。
getInput
函数用于从用户获取输入。该函数会一直循环,直到输入的数字合法为止。首先,定义了一些变量用于接收用户输入和处理输入。然后,通过调用fgets
函数从标准输入中获取用户输入并存储在缓冲区中。接着,对缓冲区进行检查,以确定输入是否包含换行符以外的其他字符。如果存在其他字符,则通过循环将多余的字符都读取并丢弃。然后,通过调用strtol
函数将缓冲区中的字符串转换为整数。在转换过程中,会检查输入是否超出范围或包含非数字字符。如果输入不合法,则继续循环,要求用户重新输入。最后,返回合法的输入数字。
test
函数用于执行自我测试。在该函数中,使用assert
宏来验证fib
函数输出的结果是否符合预期。通过调用assert
宏,如果测试不通过,程序会输出错误信息并终止。
main
函数是程序的入口函数。首先,调用test
函数执行自我测试,并打印出测试通过的消息。然后,通过调用getInput
函数获取用户输入的n。接下来,通过调用clock
函数分别记录程序开始执行和结束执行的时间。然后,调用fib
函数计算第n个斐波那契数,并将结果打印出来。最后,再次使用clock
函数记录程序结束执行时的时间,并计算出程序的运行时间。运行时间以秒为单位进行打印。
因此,这段代码实现了一个完整的斐波那契数计算程序。它可以根据用户的输入计算并打印斐波那契序列中第n个数字,并且具备输入验证和自我测试的功能。
## 阶乘
#include
int main()
{
int a[200], n, counter, temp, i;
a[0] = 1;
counter = 0;
printf("Enter a whole number to Find its Factorial: ");
scanf("%d", &n);
if (n < 0)
printf("Cannot Calculate factorials for negative numbers.");
else
{
for (; n >= 2; n--)
{
temp = 0;
for (i = 0; i <= counter; i++)
{
temp = (a[i] * n) + temp;
a[i] = temp % 10;
temp = temp / 10;
}
while (temp > 0)
{
a[++counter] = temp % 10;
temp = temp / 10;
}
}
for (i = counter; i >= 0; i--) printf("%d", a[i]);
}
return 0;
}
这段代码是一个用于计算输入数字的阶乘的程序。下面是代码的详细解释:
首先,定义了一些变量。a
是用于存储计算结果的数组,n
用于接收用户输入的数字,counter
用于记录数组中最高位的索引,temp
用于保存中间计算结果,i
用于循环计数。
在程序开始时,将数组a
的第一个元素设置为1,表示初始时的阶乘结果为1。然后将counter
设置为0,用于记录最高位的索引。
接下来,通过调用printf
函数打印提示用户输入数字的消息。然后,通过调用scanf
函数从标准输入中获取用户输入的数字,并将其存储在变量n
中。
在进行计算之前,首先对用户输入的数字进行检查。如果输入的数字小于0,则说明无法计算阶乘,因为阶乘只能应用于非负整数。在这种情况下,通过调用printf
函数打印错误消息。
如果输入的数字大于等于0,那么开始进行阶乘计算。使用一个循环,从输入的数字递减到2,每次迭代都进行一次阶乘计算。
在每次迭代中,首先将temp
设置为0,用于保存中间计算结果。然后使用一个嵌套的循环,从数组a
的第一个元素到最高位的索引,依次对数组中的每一位进行乘法运算。
在乘法运算中,将当前数组中的值与当前迭代的数字相乘,并将结果加上之前的进位temp
。然后,将得到的乘法结果的个位数保存在数组a
中的相应位置,而进位则保存在temp
中。这样,我们就更新了数组a
中的值,并将进位传递给下一位的乘法运算。
在内层循环结束后,如果temp
大于0,则说明还有进位没有处理完。在这种情况下,使用一个循环将剩余的进位依次添加到数组a
中,同时更新最高位的索引counter
。
完成所有的阶乘计算后,使用一个循环从最高位到最低位,依次打印出数组a
中的数字。注意,由于每位数字是以逆序存储的,所以需要从最高位开始打印。
最后,通过返回0来表示程序执行成功,并终止程序的执行。
因此,这段代码实现了一个计算输入数字的阶乘的程序。它通过循环和数组来进行阶乘的计算,并打印出结果。程序还包含输入验证,确保用户输入的数字是非负的。
/**
* @file
* @brief Program to identify if a number is [palindrome
* number](https://en.wikipedia.org/wiki/Palindrome) or not.
* @see project_euler/problem_4/sol1.c
*/
#include
#include
#include
bool isPalindrome(int number);
/** Driver Code */
int main()
{
assert(isPalindrome(0));
assert(isPalindrome(1));
assert(isPalindrome(12321));
assert(!isPalindrome(1234));
return 0;
}
/**
* Check given number whether is palindrome number or not
* @param number number to check
* @return `true` if given number is palindrome number
* @return `false` if number is not a palindrome number
*/
bool isPalindrome(int number)
{
int reversedNumber = 0;
int originalNumber = number;
while (number != 0)
{
int remainder = number % 10;
reversedNumber = reversedNumber * 10 + remainder;
number /= 10;
}
return originalNumber == reversedNumber;
}
这段代码是一个用于判断一个数字是否是回文数的程序。下面是代码的详细解释:
首先,包含了一些头文件(assert.h, stdbool.h, stdio.h),用于引入所需的函数和变量类型。
接下来定义了一个函数isPalindrome
,用于判断给定的数字是否是回文数。该函数接受一个整数参数number
,并返回一个布尔值。
在isPalindrome
函数内部,定义了两个整型变量reversedNumber
和originalNumber
,分别用于保存数字的反转结果和原始数字。
使用一个循环,不断地取给定数字number
的最后一位,并将其添加到reversedNumber
的末尾,直到number
变为0。
在每次迭代中,首先将number
对10取余得到最后一位数字remainder
,然后将reversedNumber
乘以10并加上remainder
,实现将最后一位数字添加到reversedNumber
的末尾。
最后,将number
除以10,实现去掉最后一位数字。
完成循环后,reversedNumber
即为数字的反转结果。
最后,比较originalNumber
和reversedNumber
是否相等,如果相等,则说明给定数字是回文数,返回true
,否则返回false
。
在main
函数中,使用assert
宏来检查isPalindrome
函数的结果是否符合预期。分别传入一些测试用例,包括0、1、12321和1234。
程序通过返回0来表示执行成功,并终止程序的执行。
因此,这段代码实现了一个判断给定数字是否是回文数的函数,并在main
函数中进行了测试。它通过将数字进行反转并与原始数字进行比较,来确定是否是回文数。
/**
* @file
* @brief Functions related to 3D quaternions and Euler angles.
* @author Krishna Vedala
*/
#include
#ifdef __arm__ // if compiling for ARM-Cortex processors
#define LIBQUAT_ARM
#include
#else
#include
#endif
#include
#include "geometry_datatypes.h"
/**
* @addtogroup quats 3D Quaternion operations
* @{
*/
/**
* Function to convert given Euler angles to a quaternion.
* \f{eqnarray*}{
* q_{0} & =
* &\cos\left(\frac{\phi}{2}\right)\cos\left(\frac{\theta}{2}\right)\cos\left(\frac{\psi}{2}\right)
* +
* \sin\left(\frac{\phi}{2}\right)\sin\left(\frac{\theta}{2}\right)\sin\left(\frac{\psi}{2}\right)\\
* q_{1} & =
* &\sin\left(\frac{\phi}{2}\right)\cos\left(\frac{\theta}{2}\right)\cos\left(\frac{\psi}{2}\right)
* -
* \cos\left(\frac{\phi}{2}\right)\sin\left(\frac{\theta}{2}\right)\sin\left(\frac{\psi}{2}\right)\\
* q_{2} & =
* &\cos\left(\frac{\phi}{2}\right)\sin\left(\frac{\theta}{2}\right)\cos\left(\frac{\psi}{2}\right)
* +
* \sin\left(\frac{\phi}{2}\right)\cos\left(\frac{\theta}{2}\right)\sin\left(\frac{\psi}{2}\right)\\
* q_{3} & =
* &\cos\left(\frac{\phi}{2}\right)\cos\left(\frac{\theta}{2}\right)\sin\left(\frac{\psi}{2}\right)
* -
* \sin\left(\frac{\phi}{2}\right)\sin\left(\frac{\theta}{2}\right)\cos\left(\frac{\psi}{2}\right)\\
* \f}
*
* @param [in] in_euler input Euler angles instance
* @returns converted quaternion
*/
quaternion quat_from_euler(const euler *in_euler)
{
quaternion out_quat;
if (!in_euler) // if null
{
fprintf(stderr, "%s: Invalid input.", __func__);
return out_quat;
}
quaternion temp;
float cy = cosf(in_euler->yaw * 0.5f);
float sy = sinf(in_euler->yaw * 0.5f);
float cp = cosf(in_euler->pitch * 0.5f);
float sp = sinf(in_euler->pitch * 0.5f);
float cr = cosf(in_euler->roll * 0.5f);
float sr = sinf(in_euler->roll * 0.5f);
temp.w = cr * cp * cy + sr * sp * sy;
temp.q1 = sr * cp * cy - cr * sp * sy;
temp.q2 = cr * sp * cy + sr * cp * sy;
temp.q3 = cr * cp * sy - sr * sp * cy;
return temp;
}
/**
* Function to convert given quaternion to Euler angles.
* \f{eqnarray*}{
* \phi & = &
* \tan^{-1}\left[\frac{2\left(q_0q_1+q_2q_3\right)}{1-2\left(q_1^2+q_2^2\right)}\right]\\
* \theta & =
* &-\sin^{-1}\left[2\left(q_0q_2-q_3q_1\right)\right]\\
* \psi & = &
* \tan^{-1}\left[\frac{2\left(q_0q_3+q_1q_2\right)}{1-2\left(q_2^2+q_3^2\right)}\right]\\
* \f}
*
* @param [in] in_quat input quaternion instance
* @returns converted euler angles
*/
euler euler_from_quat(const quaternion *in_quat)
{
euler out_euler;
if (!in_quat) // if null
{
fprintf(stderr, "%s: Invalid input.", __func__);
return out_euler;
}
out_euler.roll = atan2f(
2.f * (in_quat->w * in_quat->q1 + in_quat->q2 * in_quat->q3),
1.f - 2.f * (in_quat->q1 * in_quat->q1 + in_quat->q2 * in_quat->q2));
out_euler.pitch =
asinf(2.f * (in_quat->w * in_quat->q2 + in_quat->q1 * in_quat->q3));
out_euler.yaw = atan2f(
2.f * (in_quat->w * in_quat->q3 + in_quat->q1 * in_quat->q2),
1.f - 2.f * (in_quat->q2 * in_quat->q2 + in_quat->q3 * in_quat->q3));
return out_euler;
}
/**
* Function to multiply two quaternions.
* \f{eqnarray*}{
* \mathbf{c} & = & \mathbf{a}\otimes\mathbf{b}\\
* & = & \begin{bmatrix}a_{0} & a_{1} & a_{2} &
* a_{3}\end{bmatrix}\otimes\begin{bmatrix}b_{0} & b_{1} & b_{2} &
* b_{3}\end{bmatrix}\\
* & = &
* \begin{bmatrix}
* a_{0}b_{0}-a_{1}b_{1}-a_{2}b_{2}-a_{3}b_{3}\\
* a_{0}b_{1}+a_{1}b_{0}+a_{2}b_{3}-a_{3}b_{2}\\
* a_{0}b_{2}-a_{1}b_{3}+a_{2}b_{0}+a_{3}b_{1}\\
* a_{0}b_{3}+a_{1}b_{2}-a_{2}b_{1}+a_{3}b_{0}
* \end{bmatrix}^{T}
* \f}
*
* @param [in] in_quat1 first input quaternion instance
* @param [in] in_quat2 second input quaternion instance
* @returns resultant quaternion
*/
quaternion quaternion_multiply(const quaternion *in_quat1,
const quaternion *in_quat2)
{
quaternion out_quat;
if (!in_quat1 || !in_quat2) // if null
{
fprintf(stderr, "%s: Invalid input.", __func__);
return out_quat;
}
out_quat.w = in_quat1->w * in_quat2->w - in_quat1->q1 * in_quat2->q1 -
in_quat1->q2 * in_quat2->q2 - in_quat1->q3 * in_quat2->q3;
out_quat.q1 = in_quat1->w * in_quat2->q1 + in_quat1->q1 * in_quat2->w +
in_quat1->q2 * in_quat2->q3 - in_quat1->q3 * in_quat2->q2;
out_quat.q2 = in_quat1->w * in_quat2->q2 - in_quat1->q1 * in_quat2->q3 +
in_quat1->q2 * in_quat2->w + in_quat1->q3 * in_quat2->q1;
out_quat.q3 = in_quat1->w * in_quat2->q3 + in_quat1->q1 * in_quat2->q2 -
in_quat1->q2 * in_quat2->q1 + in_quat1->q3 * in_quat2->w;
return out_quat;
}
/** @} */
static void test()
{
quaternion quat = {0.7071f, 0.7071f, 0.f, 0.f};
euler eul = euler_from_quat(&quat);
printf("Euler: %.4g, %.4g, %.4g\n", eul.pitch, eul.roll, eul.yaw);
quaternion test_quat = quat_from_euler(&eul);
printf("Quaternion: %.4g %+.4g %+.4g %+.4g\n", test_quat.w,
test_quat.dual.x, test_quat.dual.y, test_quat.dual.z);
assert(fabsf(test_quat.w - quat.w) < .01);
assert(fabsf(test_quat.q1 - quat.q1) < .01);
assert(fabsf(test_quat.q2 - quat.q2) < .01);
assert(fabsf(test_quat.q3 - quat.q3) < .01);
}
int main()
{
test();
return 0;
}
这段代码是关于3D四元数和欧拉角的操作。它包含了以下几个函数:
quat_from_euler()
:将给定的欧拉角转换为四元数。euler_from_quat()
:将给定的四元数转换为欧拉角。quaternion_multiply()
:将两个四元数相乘,得到结果四元数。test()
函数用于测试这些函数的正确性,并进行了一些断言来验证结果。geometry_datatypes.h
)。如果你想完整地运行这段代码,需要确保所有的依赖项都已经包含并正确设置。这段代码是用来进行3D姿态表示的转换操作的。它实现了将欧拉角转换为四元数,以及将四元数转换为欧拉角的功能。
在计算机图形学和机器人学中,姿态表示是描述物体或者机器人在三维空间中的方向和位置的一种方式。欧拉角是一种常见的姿态表示方法,通过三个角度(通常是滚转、俯仰和偏航)来描述物体的旋转。而四元数是一种更高效和稳定的姿态表示方法,可以用四个实数来表示旋转。
这段代码提供了欧拉角和四元数之间的转换功能,可以在不同的姿态表示之间进行转换。通过这些转换函数,你可以将一个欧拉角表示的姿态转换为对应的四元数表示,或者将一个四元数表示的姿态转换为对应的欧拉角表示。
这些转换操作在计算机图形学、机器人学、虚拟现实等领域都有广泛的应用。它们可以用于物体的旋转和姿态插值,机器人的控制和路径规划,以及许多其他需要处理三维姿态的应用场景。
# If necessary, use the RELATIVE flag, otherwise each source file may be listed
# with full pathname. The RELATIVE flag makes it easier to extract an executable's name
# automatically.
file( GLOB APP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c )
foreach( testsourcefile ${APP_SOURCES} )
string( REPLACE ".c" "" testname ${testsourcefile} ) # File type. Example: `.c`
add_executable( ${testname} ${testsourcefile} )
if(OpenMP_C_FOUND)
target_link_libraries(${testname} OpenMP::OpenMP_C)
endif()
if(MATH_LIBRARY)
target_link_libraries(${testname} ${MATH_LIBRARY})
endif()
install(TARGETS ${testname} DESTINATION "bin/cipher") # Folder name. Do NOT include `<>`
endforeach( testsourcefile ${APP_SOURCES} )
这是一个CMakeLists.txt文件中的一段代码,用于自动化构建多个可执行文件,并将它们安装到指定的目录。
首先,通过使用"file(GLOB APP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c)“命令,找到当前源代码目录下所有以”.c"为扩展名的源文件,并将它们作为列表存储在变量APP_SOURCES中。RELATIVE标志将源文件的路径相对于当前源代码目录进行存储,方便后续使用。
然后,通过foreach循环,遍历APP_SOURCES列表中的每个源文件名(testsourcefile)。在循环中,首先通过"string(REPLACE “.c” “” testname ${testsourcefile})“命令,将testsourcefile中的”.c"替换为空字符串,得到一个不包含扩展名的文件名(testname)。
接下来,使用"add_executable(${testname} ${testsourcefile})"命令,根据源文件名生成一个可执行文件,并将该文件添加到构建系统中。
然后,根据条件判断,如果OpenMP库可用,使用"target_link_libraries( t e s t n a m e O p e n M P : : O p e n M P C ) " 命令,将 O p e n M P 库链接到可执行文件中。如果定义了 M A T H L I B R A R Y 变量,使用 " t a r g e t l i n k l i b r a r i e s ( {testname} OpenMP::OpenMP_C)"命令,将OpenMP库链接到可执行文件中。如果定义了MATH_LIBRARY变量,使用"target_link_libraries( testnameOpenMP::OpenMPC)"命令,将OpenMP库链接到可执行文件中。如果定义了MATHLIBRARY变量,使用"targetlinklibraries({testname} ${MATH_LIBRARY})"命令,将其它的数学库链接到可执行文件中。
最后,使用"install(TARGETS ${testname} DESTINATION “bin/cipher”)"命令,将生成的可执行文件安装到目标目录"bin/cipher"中。
整个循环结束后,所有的源文件都被编译为对应的可执行文件,并安装到指定目录中。
/**
* @file
* @brief An [affine cipher](https://en.wikipedia.org/wiki/Affine_cipher) is a
* letter substitution cipher that uses a linear transformation to substitute
* letters in a message.
* @details Given an alphabet of length M with characters with numeric values
* 0-(M-1), an arbitrary character x can be transformed with the expression (ax
* + b) % M into our ciphertext character. The only caveat is that a must be
* relatively prime with M in order for this transformation to be invertible,
* i.e., gcd(a, M) = 1.
* @author [Daniel Murrow](https://github.com/dsmurrow)
*/
#include /// for assertions
#include /// for IO
#include /// for div function and div_t struct as well as malloc and free
#include /// for strlen, strcpy, and strcmp
/**
* @brief number of characters in our alphabet (printable ASCII characters)
*/
#define ALPHABET_SIZE 95
/**
* @brief used to convert a printable byte (32 to 126) to an element of the
* group Z_95 (0 to 94)
*/
#define Z95_CONVERSION_CONSTANT 32
/**
* @brief a structure representing an affine cipher key
*/
typedef struct
{
int a; ///< what the character is being multiplied by
int b; ///< what is being added after the multiplication with `a`
} affine_key_t;
/**
* @brief finds the value x such that (a * x) % m = 1
*
* @param a number we are finding the inverse for
* @param m the modulus the inversion is based on
*
* @returns the modular multiplicative inverse of `a` mod `m`
*/
int modular_multiplicative_inverse(unsigned int a, unsigned int m)
{
int x[2] = {1, 0};
div_t div_result;
if (m == 0) {
return 0;
}
a %= m;
if (a == 0) {
return 0;
}
div_result.rem = a;
while (div_result.rem > 0)
{
div_result = div(m, a);
m = a;
a = div_result.rem;
// Calculate value of x for this iteration
int next = x[1] - (x[0] * div_result.quot);
x[1] = x[0];
x[0] = next;
}
return x[1];
}
/**
* @brief Given a valid affine cipher key, this function will produce the
* inverse key.
*
* @param key They key to be inverted
*
* @returns inverse of key
*/
affine_key_t inverse_key(affine_key_t key)
{
affine_key_t inverse;
inverse.a = modular_multiplicative_inverse(key.a, ALPHABET_SIZE);
// Turn negative results positive
inverse.a += ALPHABET_SIZE;
inverse.a %= ALPHABET_SIZE;
inverse.b = -(key.b % ALPHABET_SIZE) + ALPHABET_SIZE;
return inverse;
}
/**
* @brief Encrypts character string `s` with key
*
* @param s string to be encrypted
* @param key affine key used for encryption
*
* @returns void
*/
void affine_encrypt(char *s, affine_key_t key)
{
for (int i = 0; s[i] != '\0'; i++)
{
int c = (int)s[i] - Z95_CONVERSION_CONSTANT;
c *= key.a;
c += key.b;
c %= ALPHABET_SIZE;
s[i] = (char)(c + Z95_CONVERSION_CONSTANT);
}
}
/**
* @brief Decrypts an affine ciphertext
*
* @param s string to be decrypted
* @param key Key used when s was encrypted
*
* @returns void
*/
void affine_decrypt(char *s, affine_key_t key)
{
affine_key_t inverse = inverse_key(key);
for (int i = 0; s[i] != '\0'; i++)
{
int c = (int)s[i] - Z95_CONVERSION_CONSTANT;
c += inverse.b;
c *= inverse.a;
c %= ALPHABET_SIZE;
s[i] = (char)(c + Z95_CONVERSION_CONSTANT);
}
}
/**
* @brief Tests a given string
*
* @param s string to be tested
* @param a value of key.a
* @param b value of key.b
*
* @returns void
*/
void test_string(const char *s, const char *ciphertext, int a, int b)
{
char *copy = malloc((strlen(s) + 1) * sizeof(char));
strcpy(copy, s);
affine_key_t key = {a, b};
affine_encrypt(copy, key);
assert(strcmp(copy, ciphertext) == 0); // assert that the encryption worked
affine_decrypt(copy, key);
assert(strcmp(copy, s) ==
0); // assert that we got the same string we started with
free(copy);
}
/**
* @brief Test multiple strings
*
* @returns void
*/
static void tests()
{
test_string("Hello!", "&3ddy2", 7, 11);
test_string("TheAlgorithms/C", "DNC}=jHS2zN!7;E", 67, 67);
test_string("0123456789", "840,($ {ws", 91, 88);
test_string("7W@;cdeRT9uL", "JDfa*we?z&bL", 77, 76);
test_string("~Qr%^-+++$leM", "r'qC0$sss;Ahf", 8, 90);
test_string("The quick brown fox jumps over the lazy dog",
"K7: .*6<4 =-0(1 90' 5*2/, 0):- +7: 3>%& ;08", 94, 0);
test_string(
"One-1, Two-2, Three-3, Four-4, Five-5, Six-6, Seven-7, Eight-8, "
"Nine-9, Ten-10",
"H&60>\\2*uY0q\\2*p4660E\\2XYn40x\\2XDB60L\\2VDI0 "
"\\2V6B6&0S\\2%D=p;0'\\2tD&60Z\\2*6&0>j",
51, 18);
printf("All tests have successfully passed!\n");
}
/**
* @brief main function
*
* @returns 0 upon successful program exit
*/
int main()
{
tests();
return 0;
}
这段代码实现了一个仿射密码(Affine Cipher),它是一种使用线性变换来替换消息中的字母的字母替换密码。
代码中定义了一个结构体 affine_key_t
,表示仿射密码的密钥,包括两个整数 a
和 b
。函数 modular_multiplicative_inverse
用于找到关于模 m
的数 x
,使得 (a * x) % m = 1
,即计算模乘法逆元。函数 inverse_key
则用于计算给定密钥的逆密钥。
affine_encrypt
函数用于对字符串进行加密,它将字符串中的每个字符转换成相应的仿射密码字符。affine_decrypt
函数用于对仿射密码进行解密,恢复原始字符串。
测试部分的函数 test_string
用于对给定的字符串进行加密和解密,并进行断言检查是否得到了正确的结果。最后的 tests
函数用于调用 test_string
函数进行多个字符串的测试。
主函数 main
中调用了 tests
函数进行测试,并输出测试结果。
这段代码通过对字符串进行加密和解密的测试来验证仿射密码的实现是否正确。下面是代码中的几个样例测试:
输入字符串:“Hello!”,密钥参数:a=7,b=11。
预期输出密文:“&3ddy2”,解密后得到原始字符串:“Hello!”
输入字符串:“TheAlgorithms/C”,密钥参数:a=67,b=67。
预期输出密文:“DNC}=jHS2zN!7;E”,解密后得到原始字符串:“TheAlgorithms/C”
输入字符串:“0123456789”,密钥参数:a=91,b=88。
预期输出密文:“840,($ {ws”,解密后得到原始字符串:“0123456789”
输入字符串:“7W@;cdeRT9uL”,密钥参数:a=77,b=76。
预期输出密文:“JDfa*we?z&bL”,解密后得到原始字符串:“7W@;cdeRT9uL”
输入字符串:“~Qr%^-+++ l e M " ,密钥参数: a = 8 , b = 90 。预期输出密文: " r ′ q C 0 leM",密钥参数:a=8,b=90。 预期输出密文:"r'qC0 leM",密钥参数:a=8,b=90。预期输出密文:"r′qC0sss;Ahf”,解密后得到原始字符串:“~Qr%^-+++$leM”
输入字符串:“The quick brown fox jumps over the lazy dog”,密钥参数:a=94,b=0。
预期输出密文:“K7: .6<4 =-0(1 90’ 52/, 0):- +7: 3>%& ;08”,解密后得到原始字符串:“The quick brown fox jumps over the lazy dog”
输入字符串:“One-1, Two-2, Three-3, Four-4, Five-5, Six-6, Seven-7, Eight-8, Nine-9, Ten-10”,密钥参数:a=51,b=18。
预期输出密文:“H&60>\2uY0q\2p4660E\2XYn40x\2XDB60L\2VDI0 \2V6B6&0S\2%D=p;0’\2tD&60Z\2*6&0>j”,解密后得到原始字符串:“One-1, Two-2, Three-3, Four-4, Five-5, Six-6, Seven-7, Eight-8, Nine-9, Ten-10”
如果代码实现正确,所有样例测试应该通过并输出 “All tests have successfully passed!” 的提示信息。您可以在自己的开发环境中运行代码,观察输出结果是否符合预期。
/**
* @file
* @brief [ROT13](https://en.wikipedia.org/wiki/ROT13) is a simple letter
* substitution cipher that replaces a letter with the 13th letter after it in
* the alphabet.
* @details ROT13 transforms a piece of text by examining its alphabetic
* characters and replacing each one with the letter 13 places further along in
* the alphabet, wrapping back to the beginning if necessary. A becomes N, B
* becomes O, and so on up to M, which becomes Z, then the sequence continues at
* the beginning of the alphabet: N becomes A, O becomes B, and so on to Z,
* which becomes M.
* @author [Jeremias Moreira Gomes](https://github.com/j3r3mias)
*/
#include /// for IO operations
#include /// for string operations
#include /// for assert
/**
* @brief Apply the ROT13 cipher
* @param s contains the string to be processed
*/
void rot13(char *s) {
for (int i = 0; s[i]; i++) {
if (s[i] >= 'A' && s[i] <= 'Z') {
s[i] = 'A' + ((s[i] - 'A' + 13) % 26);
} else if (s[i] >= 'a' && s[i] <= 'z') {
s[i] = 'a' + ((s[i] - 'a' + 13) % 26);
}
}
}
/**
* @brief Self-test implementations
* @returns void
*/
static void test() {
char test_01[] = "The more I C, the less I see.";
rot13(test_01);
assert(strcmp(test_01, "Gur zber V P, gur yrff V frr.") == 0);
char test_02[] = "Which witch switched the Swiss wristwatches?";
rot13(test_02);
assert(strcmp(test_02, "Juvpu jvgpu fjvgpurq gur Fjvff jevfgjngpurf?") == 0);
char test_03[] = "Juvpu jvgpu fjvgpurq gur Fjvff jevfgjngpurf?";
rot13(test_03);
assert(strcmp(test_03, "Which witch switched the Swiss wristwatches?") == 0);
printf("All tests have successfully passed!\n");
}
/**
* @brief Main function
* @returns 0 on exit
*/
int main() {
test(); // run self-test implementations
return 0;
}
这段代码实现了 ROT13(ROT13)密码,它是一种简单的字母替换密码,将字母替换为字母表中后面第 13 个字母。
代码中的 rot13
函数用于应用 ROT13 密码。它遍历字符串中的每个字符,如果字符是大写字母,则将其替换为字母表中后面第 13 个字母(如果需要则回到字母表开头);如果字符是小写字母,则同样将其替换为后面第 13 个字母。该函数会原地修改输入的字符串。
代码中的 test
函数用于自我测试实现是否正确。它执行了几个样例测试,通过比较加密和解密后的字符串与预期结果来验证实现的正确性。如果所有测试都通过,则输出 “All tests have successfully passed!” 的提示信息。
代码中的测试样例如下所示:
输入字符串:“The more I C, the less I see.”
预期输出结果:“Gur zber V P, gur yrff V frr.”
输入字符串:“Which witch switched the Swiss wristwatches?”
预期输出结果:“Juvpu jvgpu fjvgpurq gur Fjvff jevfgjngpurf?”
输入字符串:“Juvpu jvgpu fjvgpurq gur Fjvff jevfgjngpurf?”
预期输出结果:“Which witch switched the Swiss wristwatches?”
如果代码实现正确,所有测试应该通过,并输出 “All tests have successfully passed!” 的提示信息。
您可以在自己的开发环境中运行代码,观察输出结果是否符合预期。
/**
* @file
* @brief Functions related to 3D vector operations.
* @author Krishna Vedala
*/
#include
#ifdef __arm__ // if compiling for ARM-Cortex processors
#define LIBQUAT_ARM
#include
#else
#include
#endif
#include
#include "geometry_datatypes.h"
/**
* @addtogroup vec_3d 3D Vector operations
* @{
*/
/**
* Subtract one vector from another. @f[
* \vec{c}=\vec{a}-\vec{b}=\left(a_x-b_x\right)\hat{i}+
* \left(a_y-b_y\right)\hat{j}+\left(a_z-b_z\right)\hat{k}@f]
* @param[in] a vector to subtract from
* @param[in] b vector to subtract
* @returns resultant vector
*/
vec_3d vector_sub(const vec_3d *a, const vec_3d *b)
{
vec_3d out;
#ifdef LIBQUAT_ARM
arm_sub_f32((float *)a, (float *)b, (float *)&out);
#else
out.x = a->x - b->x;
out.y = a->y - b->y;
out.z = a->z - b->z;
#endif
return out;
}
/**
* Add one vector to another. @f[
* \vec{c}=\vec{a}+\vec{b}=\left(a_x+b_x\right)\hat{i}+
* \left(a_y+b_y\right)\hat{j}+\left(a_z+b_z\right)\hat{k}@f]
* @param[in] a vector to add to
* @param[in] b vector to add
* @returns resultant vector
*/
vec_3d vector_add(const vec_3d *a, const vec_3d *b)
{
vec_3d out;
#ifdef LIBQUAT_ARM
arm_add_f32((float *)a, (float *)b, (float *)&out);
#else
out.x = a->x + b->x;
out.y = a->y + b->y;
out.z = a->z + b->z;
#endif
return out;
}
/**
* Obtain the dot product of two 3D vectors.
* @f[
* \vec{a}\cdot\vec{b}=a_xb_x + a_yb_y + a_zb_z
* @f]
* @param[in] a first vector
* @param[in] b second vector
* @returns resulting dot product
*/
float dot_prod(const vec_3d *a, const vec_3d *b)
{
float dot;
#ifdef LIBQUAT_ARM
arm_dot_prod_f32((float *)a, (float *)b, &dot);
#else
dot = a->x * b->x;
dot += a->y * b->y;
dot += a->z * b->z;
#endif
return dot;
}
/**
* Compute the vector product of two 3d vectors.
* @f[\begin{align*}
* \vec{a}\times\vec{b} &= \begin{vmatrix}
* \hat{i} & \hat{j} & \hat{k}\\
* a_x & a_y & a_z\\
* b_x & b_y & b_z
* \end{vmatrix}\\
* &= \left(a_yb_z-b_ya_z\right)\hat{i} - \left(a_xb_z-b_xa_z\right)\hat{j}
* + \left(a_xb_y-b_xa_y\right)\hat{k} \end{align*}
* @f]
* @param[in] a first vector @f$\vec{a}@f$
* @param[in] b second vector @f$\vec{b}@f$
* @returns resultant vector @f$\vec{o}=\vec{a}\times\vec{b}@f$
*/
vec_3d vector_prod(const vec_3d *a, const vec_3d *b)
{
vec_3d out; // better this way to avoid copying results to input
// vectors themselves
out.x = a->y * b->z - a->z * b->y;
out.y = -a->x * b->z + a->z * b->x;
out.z = a->x * b->y - a->y * b->x;
return out;
}
/**
* Print formatted vector on stdout.
* @param[in] a vector to print
* @param[in] name name of the vector
* @returns string representation of vector
*/
const char *print_vector(const vec_3d *a, const char *name)
{
static char vec_str[100]; // static to ensure the string life extends the
// life of function
snprintf(vec_str, 99, "vec(%s) = (%.3g)i + (%.3g)j + (%.3g)k\n", name, a->x,
a->y, a->z);
return vec_str;
}
/**
* Compute the norm a vector.
* @f[\lVert\vec{a}\rVert = \sqrt{\vec{a}\cdot\vec{a}} @f]
* @param[in] a input vector
* @returns norm of the given vector
*/
float vector_norm(const vec_3d *a)
{
float n = dot_prod(a, a);
#ifdef LIBQUAT_ARM
arm_sqrt_f32(*n, n);
#else
n = sqrtf(n);
#endif
return n;
}
/**
* Obtain unit vector in the same direction as given vector.
* @f[\hat{a}=\frac{\vec{a}}{\lVert\vec{a}\rVert}@f]
* @param[in] a input vector
* @returns n unit vector in the direction of @f$\vec{a}@f$
*/
vec_3d unit_vec(const vec_3d *a)
{
vec_3d n = {0};
float norm = vector_norm(a);
if (fabsf(norm) < EPSILON)
{ // detect possible divide by 0
return n;
}
if (norm != 1.F) // perform division only if needed
{
n.x = a->x / norm;
n.y = a->y / norm;
n.z = a->z / norm;
}
return n;
}
/**
* The cross product of vectors can be represented as a matrix
* multiplication operation. This function obtains the `3x3` matrix
* of the cross-product operator from the first vector.
* @f[\begin{align*}
* \left(\vec{a}\times\right)\vec{b} &= \tilde{A}_a\vec{b}\\
* \tilde{A}_a &=
* \begin{bmatrix}0&-a_z&a_y\\a_z&0&-a_x\\-a_y&a_x&0\end{bmatrix}
* \end{align*}@f]
* @param[in] a input vector
* @returns the `3x3` matrix for the cross product operator
* @f$\left(\vec{a}\times\right)@f$
*/
mat_3x3 get_cross_matrix(const vec_3d *a)
{
mat_3x3 A = {0., -a->z, a->y, a->z, 0., -a->x, -a->y, a->x, 0.};
return A;
}
/**
* Obtain the angle between two given vectors.
* @f[\alpha=acos\left(\frac{\vec{a} \cdot \vec{b}}{\lVert\vec{a}\rVert \cdot \lVert\vec{b}\rVert}\right)@f]
* @param[in] a first input vector
* @param[in] b second input vector
* @returns angle between @f$\vec{a}@f$ and @f$\vec{b}@f$ in radians
*/
double get_angle(const vec_3d *a, const vec_3d *b)
{
double alpha, cos_alpha;
float norm_a = vector_norm(a); ///< The norm of vector a
float norm_b = vector_norm(b); ///< The norm of vector b
if (fabsf(norm_a) < EPSILON || fabsf(norm_b) < EPSILON) /// detect possible division by 0 - the angle is not defined in this case
{
return NAN;
}
cos_alpha = dot_prod(a, b) / (norm_a * norm_b);
alpha = acos(cos_alpha); // delivers the radian
return alpha; // in range from -1 to 1
}
/** @} */
/**
* @brief Testing function
* @returns `void`
*/
static void test()
{
vec_3d a = {1., 2., 3.};
vec_3d b = {1., 1., 1.};
float d;
// printf("%s", print_vector(&a, "a"));
// printf("%s", print_vector(&b, "b"));
d = vector_norm(&a);
// printf("|a| = %.4g\n", d);
assert(fabsf(d - 3.742f) < 0.01);
d = vector_norm(&b);
// printf("|b| = %.4g\n", d);
assert(fabsf(d - 1.732f) < 0.01);
d = dot_prod(&a, &b);
// printf("Dot product: %f\n", d);
assert(fabsf(d - 6.f) < 0.01);
vec_3d c = vector_prod(&a, &b);
// printf("Vector product ");
// printf("%s", print_vector(&c, "c"));
assert(fabsf(c.x - (-1.f)) < 0.01);
assert(fabsf(c.y - (2.f)) < 0.01);
assert(fabsf(c.z - (-1.f)) < 0.01);
double alpha = get_angle(&a, &b);
// printf("The angle is %f\n", alpha);
assert(fabsf(alpha - 0.387597) < 0.01);
}
/**
* @brief Main function
*
* @return 0 on exit
*/
int main(void)
{
test();
return 0;
}
这是一个关于3D向量操作的函数集,用于进行向量的加减、点积、叉积、范数、单位化等操作。这些函数封装了对不同硬件平台(包括ARM处理器)的适配,并包含了一些简单的测试用例。
这些函数可以用于处理和操作3D空间中的向量,例如在计算机图形学、物理模拟、机器人学等领域中。通过这些函数,你可以进行向量的加减、点积和叉积运算,计算向量的范数(长度),以及将向量单位化为单位向量。
在代码的末尾,有一个test()
函数用于测试这些函数的正确性,并进行了一些断言来验证结果。你可以运行这段代码来执行测试,并检查断言的结果是否为真。如果断言通过,则表示函数的实现是正确的。
你可以将这段代码保存为一个C文件,并通过编译器进行编译和运行。在运行过程中,它会输出一些测试结果,并进行断言验证。如果断言通过,即表示函数实现正确,否则可能需要检查代码实现中的错误。
这段代码提供了一组用于进行3D向量操作的函数。让我们逐个解释这些函数的功能:
vector_sub()
函数用于计算两个向量的差。它将一个向量从另一个向量中减去,并返回结果向量。vector_add()
函数用于计算两个向量的和。它将两个向量相加,并返回结果向量。dot_prod()
函数用于计算两个向量的点积。它将两个向量的对应分量相乘,并将结果累加得到一个标量值。vector_prod()
函数用于计算两个向量的叉积。它根据叉积的定义,将两个向量的分量进行组合计算,并返回结果向量。print_vector()
函数用于格式化输出一个向量的值。它将向量的分量格式化为字符串,并返回格式化后的字符串表示。vector_norm()
函数用于计算一个向量的范数(长度)。它使用点积的结果,并将结果开方得到向量的长度。unit_vec()
函数用于计算一个向量的单位向量。它将给定的向量除以其长度,从而获得与原向量方向相同但长度为1的单位向量。get_cross_matrix()
函数用于根据给定向量生成一个表示叉积运算的3x3矩阵。它将向量的分量按照叉积的定义填充到矩阵中。get_angle()
函数用于计算两个向量之间的夹角。它使用点积和向量长度的计算结果,通过反余弦函数来计算夹角的弧度值。在代码的结尾,还有一个 test()
函数用于测试这些函数的正确性,并进行了一些断言来验证结果。你可以运行这段代码来执行测试,并检查断言的结果是否为真。
这些函数和操作可以应用于许多需要进行3D向量计算的场景,例如计算机图形学中的向量变换、物理模拟中的力和运动计算、机器人学中的位姿分析等。它们提供了一组基本的操作函数,方便处理和操作3D空间中的向量数据。
/**
* @file tic-tac-toe.c
* @author [vivekboss99](github.com/vivekboss99)
* @author [Krishna Vedala](https://github.com/kvedala)
* @brief [Tic-Tac-Toe game](https://en.wikipedia.org/wiki/Tic-tac-toe)
* implementation in C
* @details Tic-Tac-Toe Game, where the user can decide to play with the
* computer(single player mode) or with other user(double player mode), the
* code as an array named 'game_table' which is the table and user needs to enter the
* position inside the array(from 1-9) where he/she wants to place 'X' or 'O' on the
* table.
*/
#include
#include
#include
#include
// Functions Declarations
static void singlemode();
static void doublemode();
static void placex(int); // used for placing position of X by the 1st player
static void place(); // used by the computer to place O
static void placey(int); // used in Double Player mode by the 2nd player to
// place the position of O
int checkwin(); // checks everytime when a player or computer places 'X' or 'O'
/** Tic-Tac-Toe table, so basically we are using variable 'game_table' as the table(size:3X3) and
* updating it regularly
*/
static char game_table[9];
/**
* Main program function.
* @returns 0 on clean exit.
* @note No checks are included for program execution failures!
*/
int main()
{
srand( (unsigned int)time(NULL));
int l = 0;
do
{
int n = 0;
// filling the table with multiple asterisks
for (int i = 0; i < 9; i++) game_table[i] = '*';
// displaying the main menu
printf("***************************************\n");
printf("*************TIC TAC TOE***************\n");
printf("***************************************\n");
printf("***********1. YOU vs COMPUTER ***********\n");
printf("***********2. YOU vs PLAYER ***********\n");
printf("***********3.EXIT *********************\n");
printf("Enter your choice : ");
scanf("%d", &n);
switch (n) // switch case to select between single player mode or
// double player mode
{
case 1:
singlemode();
break;
case 2:
doublemode();
break;
default:
printf("THANK YOU and EXIT!");
}
printf("Next game ? : ");
printf("Enter 1 – YES and 0 - NO ");
scanf("%d", &l);
} while (l == 1);
return 0;
}
/**
* @brief Implementation of game vs computer
*
* @returns None
*/
void singlemode()
{
int m;
int k = 0;
int table_fill_count=0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%c ", game_table[k]);
k++;
}
printf("\n");
}
for (int x = 1; x < 10; x++)
{
k = 0;
printf("Where would you like to place 'x' ");
scanf("%d", &m);
placex(m);
if(table_fill_count<4)
{
place();
}
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%c ", game_table[k]);
k++;
}
printf("\n");
}
table_fill_count++;
int o = checkwin();
if (o == -1 || o == -2)
{
if (o == -1)
{
printf("YOU WIN\n");
}
if (o == -2)
{
printf("YOU LOSE\n");
}
break;
}
if (table_fill_count==4)
{
printf("\nDRAW ");
break;
}
}
}
/**
* @brief Implementation of game vs another player.
*
* @returns None
*/
void doublemode()
{
int m;
int e1;
int k = 0;
int doublemode_table_count=0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%c ", game_table[k]);
k++;
}
printf("\n");
}
for (int x = 1; x < 10; x++)
{
k = 0;
printf("PLAYER1 - where would you like to place 'x' : ");
scanf("%d", &m);
placex(m);
if(doublemode_table_count<4)
{
printf("PLAYER2 - where would you like to place 'o' : ");
scanf("%d", &e1);
placey(e1);
}
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%c ", game_table[k]);
k++;
}
printf("\n");
}
doublemode_table_count++;
int o = checkwin();
if (o == -1 || o == -2)
{
if (o == -1)
{
printf("Player 1 WIN\n");
}
if (o == -2)
{
printf("Player 2 WIN\n");
}
break;
}
if (doublemode_table_count==4)
{
printf("\nDRAW ");
break;
}
}
}
int check_placex(){
char input[50];
int n1;
while (1){
fgets(input,49,stdin);
if ( strlen(input) > 2 || strlen(input) == 0){
fprintf(stderr,"Invalid move, Enter number 1 - 9: ");
continue;
}
if(sscanf(input,"%d",&n1) != 1){
fprintf(stderr,"Invalid move, Enter number 1 - 9: ");
continue;
}
if ((game_table[n1-1] == 'x') || (game_table[n1-1]) == 'o' || (n1== 0)){
fprintf(stderr,"Already allocated, Enter number: ");
continue;
}
return n1;
}
}
/**
* @brief Update table by placing an `X`
*
* @param m location to place `X`
*
* @returns None
*/
void placex(int m)
{
int n1 = 0;
if (m >= 1 && m <= 9)
{
if (game_table[m - 1] != 'x' && game_table[m - 1] != 'o')
{
game_table[m - 1] = 'x';
}
else
{
int n = check_placex();
placex(n);
}
}
else
{
int n = check_placex();
placex(n);
}
}
/**
* @brief Update table by placing an `O`
*
* @returns None
*/
void place()
{
int e = rand() % 9;
if (e >= 0)
{
if (game_table[e] != 'x' && game_table[e] != 'o')
{
game_table[e] = 'o';
printf("\n Computer placed at %d position\n", e + 1);
}
else
{
place();
}
}
}
/**
* @brief Update table by placing an `O`
*
* @param e1 location to place `O`
*
* @returns None
*/
void placey(int e1)
{
int n1 = 0;
if (e1 >= 1 && e1 <= 9)
{
if (game_table[e1 - 1] != 'x' && game_table[e1 - 1] != 'o')
{
game_table[e1 - 1] = 'o';
}
else
{
int n = check_placex();
placex(n);
}
}
else
{
int n = check_placex();
placex(n);
}
}
/**
* @brief Implementation of win conditon checker for 'X' or 'O' whenever the table is updated
*
* @returns -1: if 'X' won
* @returns -2: if 'O' won
* @returns 0: if there is no win condition for 'X' or 'O'
*/
int checkwin()
{
if (game_table[0] == game_table[1] && game_table[1] == game_table[2])
{
if (game_table[0] == 'x' && game_table[1] == 'x' &&
game_table[2] == 'x')
{
return -1;
}
if (game_table[0] == 'o' && game_table[1] == 'o' &&
game_table[2] == 'o')
{
return -2;
}
}
else if (game_table[0] == game_table[4] && game_table[4] == game_table[8])
{
if (game_table[0] == 'x' && game_table[4] == 'x' &&
game_table[8] == 'x')
{
return -1;
}
if (game_table[0] == 'o' && game_table[4] == 'o' &&
game_table[8] == 'o')
{
return -2;
}
}
else if (game_table[0] == game_table[3] && game_table[3] == game_table[6])
{
if (game_table[0] == 'x' && game_table[3] == 'x' &&
game_table[6] == 'x')
{
return -1;
}
if (game_table[0] == 'o' && game_table[3] == 'o' &&
game_table[6] == 'o')
{
return -2;
}
}
else if (game_table[3] == game_table[4] && game_table[4] == game_table[5])
{
if (game_table[3] == 'x' && game_table[4] == 'x' &&
game_table[5] == 'x')
{
return -1;
}
if (game_table[3] == 'o' && game_table[4] == 'o' &&
game_table[5] == 'o')
{
return -2;
}
}
else if (game_table[6] == game_table[7] && game_table[7] == game_table[8])
{
if (game_table[6] == 'x' && game_table[7] == 'x' &&
game_table[8] == 'x')
{
return -1;
}
if (game_table[6] == 'o' && game_table[7] == 'o' &&
game_table[8] == 'o')
{
return -2;
}
}
else if (game_table[1] == game_table[4] && game_table[4] == game_table[7])
{
if (game_table[1] == 'x' && game_table[4] == 'x' &&
game_table[7] == 'x')
{
return -1;
}
if (game_table[1] == 'o' && game_table[4] == 'o' &&
game_table[7] == 'o')
{
return -2;
}
}
else if (game_table[2] == game_table[5] && game_table[5] == game_table[8])
{
if (game_table[2] == 'x' && game_table[5] == 'x' &&
game_table[8] == 'x')
{
return -1;
}
if (game_table[2] == 'o' && game_table[5] == 'o' &&
game_table[8] == 'o')
{
return -2;
}
}
else if (game_table[2] == game_table[4] && game_table[4] == game_table[6])
{
if (game_table[2] == 'x' && game_table[4] == 'x' &&
game_table[6] == 'x')
{
return -1;
}
if (game_table[2] == 'o' && game_table[4] == 'o' &&
game_table[6] == 'o')
{
return -2;
}
}
return 0;
}
这段代码实现了井字游戏(Tic-Tac-Toe)的C语言版本。玩家可以选择与计算机进行单人模式,或与其他玩家进行双人模式。游戏使用一个3x3的数组game_table
作为游戏表格,玩家需要在数组中输入位置(1-9)来放置’X’或’O’。
代码主要包含以下函数:
singlemode()
:单人模式游戏的实现函数。玩家和计算机依次进行操作,每次操作后刷新游戏表格,并检查是否有玩家赢得比赛或平局。doublemode()
:双人模式游戏的实现函数。两个玩家依次进行操作,每次操作后刷新游戏表格,并检查是否有玩家赢得比赛或平局。placex(int m)
:根据传入的位置m,在游戏表格中放置’X’。place()
:计算机生成随机位置,在游戏表格中放置’O’。placey(int e1)
:根据传入的位置e1,在游戏表格中放置’O’。checkwin()
:检查游戏表格中是否存在玩家或计算机赢得比赛的情况,返回相应的结果(-1代表玩家赢,-2代表计算机赢,0代表没有赢家)。主程序通过循环控制实现多轮游戏的进行,玩家选择是否进行下一局游戏。
这是一个使用C语言实现的井字棋游戏(Tic-Tac-Toe)。游戏提供了两种模式:单人模式(用户与计算机对战)和双人模式(用户与其他玩家对战)。
游戏使用一个3x3的字符数组game_table
作为游戏棋盘,用户需要输入位置(1-9)来在棋盘上放置’X’或’O’。游戏通过不断更新棋盘数组来进行游戏进程的展示和判断胜负。
游戏主函数main()
中,利用循环让用户选择游戏模式并进行多轮游戏。在游戏模式选择后,会调用相应的函数进行游戏。
singlemode()
函数实现了单人模式。用户和计算机轮流在棋盘上放置’X’和’O’,直到有一方获胜或棋盘填满。doublemode()
函数实现了双人模式。两个玩家轮流在棋盘上放置’X’和’O’,直到有一方获胜或棋盘填满。placex()
、place()
和placey()
函数来更新棋盘数组。这些函数会检查用户输入的位置是否合法,并进行相应的处理。checkwin()
函数用于检查是否有一方获胜。通过判断棋盘数组中的元素是否满足获胜条件来确定胜负,如果有一方获胜则返回-1或-2,如果没有获胜则返回0。在这个井字棋游戏中,判断胜利条件的方法是通过检查棋盘数组中的元素是否满足获胜的条件。以下是判断胜利条件的规则:
checkwin()
函数中,按照上述规则进行判断。如果满足以上任意一种获胜条件,返回-1表示’X’获胜,返回-2表示’O’获胜。如果没有任何获胜条件满足且棋盘已满,则返回0表示平局。在这个井字棋游戏中,判断平局的条件是当棋盘填满时,即所有位置都被玩家放置了’X’或’O’,但没有任何一方获胜。
在singlemode()
和doublemode()
函数中,每当玩家和计算机或者两个玩家放置一个’X’或’O’后,会检查棋盘是否已经填满。如果棋盘填满且没有任何一方获胜,就会判定为平局。
在checkwin()
函数中,也有一个条件用于判断平局。如果在检查获胜条件之前棋盘已经填满了(即执行到第四轮放置’X’或’O’),则返回0表示平局。
总结起来,当棋盘填满且没有任何一方获胜时,判定为平局。
## 游戏逻辑
要实现井字棋游戏的逻辑,可以按照以下步骤进行:
以下是一个示例的井字棋游戏逻辑的伪代码实现:
定义全局变量:
棋盘大小:ROWS = 3,COLS = 3
棋盘数组:game_table[ROWS][COLS]
定义函数:
主函数:
初始化棋盘数组
游戏循环:
选择游戏模式
如果选择单人模式:
调用单人模式函数
否则,如果选择双人模式:
调用双人模式函数
询问是否继续游戏,如果不继续:
退出游戏
单人模式函数:
初始化游戏状态
游戏循环:
显示当前棋盘状态
如果轮到玩家:
提示玩家输入位置
如果输入合法:
更新棋盘数组
检查是否获胜或平局
如果获胜或平局:
显示结果并结束游戏
否则:
提示重新输入
否则,如果轮到计算机:
计算机随机选择位置
更新棋盘数组
检查是否获胜或平局
如果获胜或平局:
显示结果并结束游戏
双人模式函数:
初始化游戏状态
游戏循环:
显示当前棋盘状态
提示当前玩家输入位置
如果输入合法:
更新棋盘数组
检查是否获胜或平局
如果获胜或平局:
显示结果并结束游戏
否则,切换到另一个玩家的回合
否则:
提示重新输入
更新棋盘数组函数:
接受位置和当前玩家的参数
根据位置更新棋盘数组
检查是否获胜函数:
检查横向、纵向和对角线的连线是否满足获胜条件
如果有一方获胜:
返回-1或-2分别表示'X'或'O'获胜
否则,如果棋盘已满:
返回0表示平局
否则:
返回-3表示游戏继续
提示玩家输入位置函数:
提示玩家输入位置
接受输入并判断合法性,直到输入合法为止
显示当前棋盘状态函数:
遍历棋盘数组,打印当前棋盘状态
这只是一个简单的示例,实际的游戏逻辑可能会更复杂,还可以根据需要进行扩展和优化。你可以根据这个伪代码来实现具体的游戏逻辑,并根据需要添加错误处理、界面美化等功能。
游戏逻辑的基本结构可以分为以下几个步骤:
初始化游戏状态
循环:
显示游戏状态
如果是玩家回合:
等待玩家输入
更新游戏状态
检查胜负
如果有一方获胜或平局:
结束游戏并显示结果
否则:
切换到下一个回合的玩家
否则,如果是计算机回合:
计算机执行下棋操作
更新游戏状态
检查胜负
如果有一方获胜或平局:
结束游戏并显示结果
否则:
切换到下一个回合的玩家
结束游戏
这个基本结构可以根据具体的编程语言和需求进行具体实现和优化。在实现游戏逻辑时,需要考虑玩家输入的合法性、胜负的判断逻辑、游戏结果的显示等方面。
/**
* @file
* @brief C implementation of [Hangman Game](https://en.wikipedia.org/wiki/Hangman_(game))
* @details
* Simple, readable version of hangman.
* Changed graphic to duck instead of traditional stick figure (same number of guesses).
* @author [AtlantaEmrys2002](https://github.com/AtlantaEmrys2002)
*/
#include /// for main() - tolower()
#include /// for main(), new_word(), new_guess(), won() - I/O operations
#include /// for all functions - exit(), rand() and file functions
#include /// for main() - for string operations strlen, strchr, strcpy
#include /// for new_game() - used with srand() for declaring new game instance
/*
* @brief game_instance structure that holds current state of game
*/
struct game_instance{
char current_word[30]; ///< word to be guessed by player
char hidden[30]; ///< hidden version of word that is displayed to player
int size; ///< size of word
int incorrect; ///< number of incorrect guesses
char guesses[25]; ///< previous guesses
int guesses_size; ///< size of guesses array
};
// function prototypes
struct game_instance new_game(void); // creates a new game
int new_guess(char, const char guesses[], int size); // checks if player has already played letter
int in_word(char, const char word[], unsigned int size); // checks if letter is in word
void picture(int score); // outputs image of duck (instead of hang man)
void won(const char word[], int score); // checks if player has won or lost
/**
* @brief Main Function
* @returns 0 on exit
*/
int main() {
struct game_instance game = new_game(); // new game created
char guess; // current letter guessed by player
// main loop - asks player for guesses
while ((strchr(game.hidden, '_') != NULL) && game.incorrect <= 12) {
do {
printf("\n****************************\n");
printf("Your word: ");
for (int i = 0; i < game.size; i++) {
printf("%c ", game.hidden[i]);
}
if (game.guesses_size > 0) {
printf("\nSo far, you have guessed: ");
for (int i = 0; i < game.guesses_size; i++) {
printf("%c ", game.guesses[i]);
}
}
printf("\nYou have %d guesses left.", (12 - game.incorrect));
printf("\nPlease enter a letter: ");
scanf(" %c", &guess);
guess = tolower(guess);
} while (new_guess(guess, game.guesses, game.guesses_size) != -1);
game.guesses[game.guesses_size] = guess; // adds new letter to guesses array
game.guesses_size++; // updates size of guesses array
if (in_word(guess, game.current_word, game.size) == 1) {
printf("That letter is in the word!");
for (int i = 0; i < game.size; i++) {
if ((game.current_word[i]) == guess) {
game.hidden[i] = guess;
}
}
} else {
printf("That letter is not in the word.\n");
(game.incorrect)++;
}
picture(game.incorrect);
}
won(game.current_word, game.incorrect);
return 0;
}
/**
* @brief checks if letter has been guessed before
* @param new_guess letter that has been guessed by player
* @param guesses array of player's previous guesses
* @param size size of guesses[] array
* @returns 1 if letter has been guessed before
* @returns -1 if letter has not been guessed before
*/
int new_guess(char new_guess, const char guesses[], int size) {
for (int j = 0; j < size; j++) {
if (guesses[j] == new_guess) {
printf("\nYou have already guessed that letter.");
return 1;
}
}
return -1;
}
/**
* @brief checks if letter is in current word
* @param letter letter guessed by player
* @param word current word
* @param size length of word
* @returns 1 if letter is in word
* @returns -1 if letter is not in word
*/
int in_word(char letter, const char word[], unsigned int size) {
for (int i = 0; i < size; i++) {
if ((word[i]) == letter) {
return 1;
}
}
return -1;
}
/**
* @brief creates a new game - generates a random word and stores in global variable current_word
* @returns current_game - a new game instance containing randomly selected word, its length and hidden version of word
*/
struct game_instance new_game() {
char word[30]; // used throughout function
FILE *fptr;
fptr = fopen("games/words.txt", "r");
if (fptr == NULL){
fprintf(stderr, "File not found.\n");
exit(EXIT_FAILURE);
}
// counts number of words in file - assumes each word on new line
int line_number = 0;
while (fgets(word, 30, fptr) != NULL) {
line_number++;
}
rewind(fptr);
// generates random number
int random_num;
srand(time(NULL));
random_num = rand() % line_number;
// selects randomly generated word
int s = 0;
while (s <= random_num){
fgets(word, 30, fptr);
s++;
}
// formats string correctly
if (strchr(word, '\n') != NULL){
word[strlen(word) - 1] = '\0';
}
fclose(fptr);
// creates new game instance
struct game_instance current_game;
strcpy(current_game.current_word, word);
current_game.size = strlen(word);
for (int i = 0; i < (strlen(word)); i++) {
current_game.hidden[i] = '_';
}
current_game.incorrect = 0;
current_game.guesses_size = 0;
return current_game;
}
/**
* @brief checks if player has won or lost
* @param word the word player has attempted to guess
* @param score how many incorrect guesses player has made
* @returns void
*/
void won(const char word[], int score) {
if (score > 12) {
printf("\nYou lost! The word was: %s.\n", word);
}
else {
printf("\nYou won! You had %d guesses left.\n", (12 - score));
}
}
/*
* @brief gradually draws duck as player gets letters incorrect
* @param score how many incorrect guesses player has made
* @returns void
*/
void picture(int score) {
switch(score) {
case 12:
printf("\n _\n"
" __( ' )> \n"
" \\_ < _ ) ");
break;
case 11:
printf("\n _\n"
" __( ' )\n"
" \\_ < _ ) ");
break;
case 10:
printf("\n _\n"
" __( )\n"
" \\_ < _ ) ");
break;
case 9:
printf("\n \n"
" __( )\n"
" \\_ < _ ) ");
break;
case 8:
printf("\n \n"
" __( \n"
" \\_ < _ ) ");
break;
case 7:
printf("\n \n"
" __ \n"
" \\_ < _ ) ");
break;
case 6:
printf("\n \n"
" _ \n"
" \\_ < _ ) ");
break;
case 5:
printf("\n \n"
" _ \n"
" _ < _ ) ");
break;
case 4:
printf("\n \n"
" \n"
" _ < _ ) ");
break;
case 3:
printf("\n \n"
" \n"
" < _ ) ");
break;
case 2:
printf("\n \n"
" \n"
" _ ) ");
break;
case 1:
printf("\n \n"
" \n"
" ) ");
break;
case 0:
break;
default:
printf("\n _\n"
" __( ' )> QUACK!\n"
" \\_ < _ ) ");
break;
}
}
这是一个用C语言实现的简单的Hangman(猜词游戏)的代码示例。该游戏使用了一个结构体game_instance
来保存游戏的当前状态。
new_game()
函数用于创建一个新的游戏实例,它从文件中随机选择一个单词作为要猜的单词,并初始化游戏状态。new_guess()
函数用于检查玩家是否已经猜过某个字母。in_word()
函数用于检查猜测的字母是否在要猜的单词中。picture()
函数根据玩家的错误猜测次数输出对应的图形。won()
函数用于判断玩家是否胜利或失败。main()
函数是游戏的主函数,包括主循环。在主循环中,玩家输入字母进行猜测,根据猜测结果更新游戏状态,并判断是否获胜或失败。实现游戏逻辑主要涉及以下几个方面:
具体实现游戏逻辑的方式会根据编程语言和游戏设计的要求而有所不同。你可以使用条件语句、循环、函数等编程结构来实现上述步骤。在实现过程中,可以利用数据结构、字符串处理、随机数生成等功能来简化和优化游戏逻辑的实现。
以下是一种基本的游戏逻辑实现步骤:
游戏逻辑的实现步骤可以根据具体的游戏设计和编程语言而有所不同,但是下面是一个通用的游戏逻辑实现的基本步骤:
这是一个C语言实现的简单的“Hangman”游戏。游戏初始化的过程在new_game()
函数中完成,具体步骤如下:
fopen()
函数打开文件,文件名为games/words.txt
。exit()
函数退出程序。fgets()
函数逐行读取文件,计算文件中的单词行数。srand()
函数结合time(NULL)
函数生成随机数的种子。rand()
函数生成一个随机数,范围为0到单词行数之间的整数。fgets()
函数跳过文件中的前几行,直到达到随机数所指定的行。word
数组中。game_instance
结构,并将选定的单词、单词长度和隐藏版本的单词存储在相应的成员变量中。这个初始化过程确保了游戏开始时随机选择一个单词,并将游戏状态初始化为初始值。游戏的主循环在主函数中执行,玩家可以进行猜测,直到猜测正确或错误次数超过限制。在游戏结束后,调用won()
函数根据猜测结果输出相应的信息。new_guess()
函数用于检查玩家是否已经猜过某个字母,in_word()
函数用于检查猜测的字母是否在单词中。picture()
函数用于根据错误猜测的次数显示相应的图形。
对于这个Hangman游戏的C实现,你可以使用以下测试样例:
测试样例1:
输入:D
输入:O
输入:G
输入:C
输入:A
输入:T
输入:S
输入:R
输入:N
输入:E
输入:Y
输入:H
输入:J
输入:L
输入:B
输入:M
输入:U
输入:F
输入:I
输入:K
输入:Q
输入:P
输入:W
输入:V
输入:X
输入:Z
预期输出:
Your word: _ _ _ _
You have 12 guesses left.
Please enter a letter: D
That letter is not in the word.
_
__( )
_ < _ )
Your word: _ _ _ _
So far, you have guessed: D
You have 11 guesses left.
Please enter a letter: O
That letter is in the word!
_
__( ’ )
_ < _ )
Your word: _ _ _ _
So far, you have guessed: D O
You have 10 guesses left.
Please enter a letter: G
That letter is not in the word.
_
__( ’ )
_ < _ )
Your word: _ _ _ _
So far, you have guessed: D O G
You have 9 guesses left.
Please enter a letter: C
That letter is not in the word.
_
__( ’ )
_ < _ )
Your word: _ _ _ _
So far, you have guessed: D O G C
You have 8 guesses left.
Please enter a letter: A
That letter is in the word!
_
__( ’ )
_ < _ )
…
预期输出应该是根据你的猜测和游戏规则逐步更新的,直到你赢得游戏或者猜测次数用尽。你可以根据需要进行进一步的测试,尝试猜测不同的字母,并观察游戏的输出结果。
/**
* @file
* @author [Krishna Vedala](https://github.com/kvedala)
* @brief Implementation of
* [Spirograph](https://en.wikipedia.org/wiki/Spirograph)
*
* @details
* Implementation of the program is based on the geometry shown in the figure
* below:
*
*
*/
#define _USE_MATH_DEFINES /**< required for MSVC compiler */
#include
#include
#include
#include
#include
/** Generate spirograph curve into arrays `x` and `y` such that the i^th point
* in 2D is represented by `(x[i],y[i])`. The generating function is given by:
* \f{eqnarray*}{
* x &=& R\left[ (1-k) \cos (t) + l\cdot k\cdot\cos \left(\frac{1-k}{k}t\right)
* \right]\\
* y &=& R\left[ (1-k) \sin (t) - l\cdot k\cdot\sin \left(\frac{1-k}{k}t\right)
* \right] \f}
* where
* * \f$R\f$ is the scaling parameter that we will consider \f$=1\f$
* * \f$l=\frac{\rho}{r}\f$ is the relative distance of marker from the centre
* of inner circle and \f$0\le l\le1\f$
* * \f$\rho\f$ is physical distance of marker from centre of inner circle
* * \f$r\f$ is the radius of inner circle
* * \f$k=\frac{r}{R}\f$ is the ratio of radius of inner circle to outer circle
* and \f$0
void spirograph(double *x, double *y, double l, double k, size_t N, double rot)
{
double dt = rot * 2.f * M_PI / N;
double t = 0.f, R = 1.f;
const double k1 = 1.f - k;
for (size_t dk = 0; dk < N; dk++, t += dt)
{
x[dk] = R * (k1 * cos(t) + l * k * cos(k1 * t / k));
y[dk] = R * (k1 * sin(t) - l * k * sin(k1 * t / k));
}
}
/**
* @brief Test function to save resulting points to a CSV file.
*
*/
void test(void)
{
size_t N = 500;
double l = 0.3, k = 0.75, rot = 10.;
char fname[50];
snprintf(fname, 50, "spirograph_%.2f_%.2f_%.2f.csv", l, k, rot);
FILE *fp = fopen(fname, "wt");
if (!fp)
{
perror(fname);
exit(EXIT_FAILURE);
}
double *x = (double *)malloc(N * sizeof(double));
double *y = (double *)malloc(N * sizeof(double));
spirograph(x, y, l, k, N, rot);
for (size_t i = 0; i < N; i++)
{
fprintf(fp, "%.5g, %.5g", x[i], y[i]);
if (i < N - 1)
{
fputc('\n', fp);
}
}
fclose(fp);
free(x);
free(y);
}
#ifdef USE_GLUT // this is set by CMAKE automatically, if available
#ifdef __APPLE__
#include // include path on Macs is different
#else
#include
#endif
static bool paused = 0; /**< flag to set pause/unpause animation */
static const int animation_speed = 25; /**< animation delate in ms */
static const double step = 0.01; /**< animation step size */
static double l_ratio = 0.1; /**< the l-ratio defined in docs */
static double k_ratio = 0.1; /**< the k-ratio defined in docs */
static const double num_rot = 20.; /**< number of rotations to simulate */
/** A wrapper that is not available in all GLUT implementations.
*/
static inline void glutBitmapString(void *font, char *string)
{
for (char *ch = string; *ch != '\0'; ch++) glutBitmapCharacter(font, *ch);
}
/**
* @brief Function to graph (x,y) points on the OpenGL graphics window.
*
* @param x array containing absicca of points (must be pre-allocated)
* @param y array containing ordinates of points (must be pre-allocated)
* @param N number of points in the arrays
*/
void display_graph(const double *x, const double *y, size_t N, double l,
double k)
{
glClearColor(1.0f, 1.0f, 1.0f,
0.0f); // Set background color to white and opaque
glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer (background)
if (x && y)
{
glBegin(GL_LINES); // draw line segments
glColor3f(0.f, 0.f, 1.f); // blue
glPointSize(2.f); // point size in pixels
for (size_t i = 1; i < N; i++)
{
glVertex2f(x[i - 1], y[i - 1]); // line from
glVertex2f(x[i], y[i]); // line to
}
glEnd();
}
glColor3f(0.f, 0.f, 0.f);
char buffer[20];
snprintf(buffer, 20, "l = %.3f", l);
glRasterPos2f(-.85, .85);
glutBitmapString(GLUT_BITMAP_HELVETICA_18, buffer);
snprintf(buffer, 20, "k = %.3f", k);
glRasterPos2f(-.85, .75);
glutBitmapString(GLUT_BITMAP_HELVETICA_18, buffer);
glutSwapBuffers();
}
/**
* @brief Test function with animation
*
*/
void test2(void)
{
const size_t N = 1000; // number of samples
static bool direction1 = true; // increment if true, otherwise decrement
static bool direction2 = true; // increment if true, otherwise decrement
double *x = (double *)malloc(N * sizeof(double));
double *y = (double *)malloc(N * sizeof(double));
spirograph(x, y, l_ratio, k_ratio, N, num_rot);
display_graph(x, y, N, l_ratio, k_ratio);
free(x); // free dynamic memories
free(y);
if (paused)
// if paused, do not update l_ratio and k_ratio
return;
if (direction1) // increment k_ratio
{
if (k_ratio >= (1.f - step)) // maximum limit
direction1 = false; // reverse direction of k_ratio
else
k_ratio += step;
}
else // decrement k_ratio
{
if (k_ratio <= step) // minimum limit
{
direction1 = true; // reverse direction of k_ratio
if (direction2) // increment l_ratio
{
if (l_ratio >= (1.f - step)) // max limit of l_ratio
direction2 = false; // reverse direction of l_ratio
else
l_ratio += step;
}
else // decrement l_ratio
{
if (l_ratio <= step) // minimum limit of l_ratio
direction2 = true; // reverse direction of l_ratio
else
l_ratio -= step;
}
}
else // no min limit of k_ratio
k_ratio -= step;
}
}
/**
* @brief GLUT timer callback function to add animation delay.
*/
void timer_cb(int id)
{
glutPostRedisplay();
glutTimerFunc(animation_speed, timer_cb, 0);
}
/**
* @brief Keypress event call back function.
*
* @param key ID of the key pressed
* @param x mouse pointer position at event
* @param y mouse pointer position at event
*/
void keyboard_cb(unsigned char key, int x, int y)
{
switch (key)
{
case ' ': // spacebar toggles pause
paused = !paused; // toggle
break;
case '+': // up arrow key
k_ratio += step;
display_graph(NULL, NULL, 1, l_ratio, k_ratio);
break;
case '_': // down arrow key
k_ratio -= step;
display_graph(NULL, NULL, 1, l_ratio, k_ratio);
break;
case '=': // left arrow key
l_ratio += step;
display_graph(NULL, NULL, 1, l_ratio, k_ratio);
break;
case '-': // right arrow key
l_ratio -= step;
display_graph(NULL, NULL, 1, l_ratio, k_ratio);
break;
case 0x1B: // escape key exits
exit(EXIT_SUCCESS);
}
}
#endif
/** Main function */
int main(int argc, char **argv)
{
test();
#ifdef USE_GLUT
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutCreateWindow("Spirograph");
glutInitWindowSize(400, 400);
// glutIdleFunc(glutPostRedisplay);
glutTimerFunc(animation_speed, timer_cb, 0);
glutKeyboardFunc(keyboard_cb);
glutDisplayFunc(test2);
glutMainLoop();
#endif
return 0;
}
这段代码实现了Spirograph(旋转曲线)的绘制功能。Spirograph是一种绘图工具,通过在内园上放置一个小块(marker),然后让它随时间旋转,可以绘制出各种华丽的旋转曲线。在这个程序中,根据给定的参数l和k,生成了一条Spirograph曲线,并将结果保存到CSV文件中。
代码中主要的函数是spirograph
和test
:
spirograph
函数根据给定的l和k参数,以及采样点数N和旋转次数num_rot,生成Spirograph曲线的(x, y)坐标数据。test
函数是一个测试函数,它调用spirograph
函数生成Spirograph曲线,并将结果保存到一个CSV文件中。如果编译时定义了USE_GLUT
宏,并且已经安装了GLUT库,那么代码中还包含了一些用于在OpenGL图形窗口中显示Spirograph曲线和实现动画效果的函数。如果不需要OpenGL图形界面,可以直接运行test
函数进行曲线生成和保存,不需要使用GLUT部分的代码。
总而言之,这段代码提供了一个生成Spirograph曲线并可视化的功能,可以根据需要选择是否使用OpenGL图形界面。
除了主要的函数以外,代码还包含了一些辅助函数和全局变量。
辅助函数:
snprintf
函数用于生成保存CSV文件的文件名,格式为spirograph_%.2f_%.2f_%.2f.csv
,其中%.2f表示保留两位小数的浮点数。display_graph
函数用于在OpenGL图形窗口中绘制Spirograph曲线。它使用OpenGL的绘图函数来绘制线段,并使用glRasterPos2f
和glutBitmapString
函数在窗口中显示l和k参数的值。glutBitmapString
是一个不在所有GLUT实现中都可用的包装函数,用于绘制字符串。全局变量:
paused
是一个布尔变量,用于指示动画是否处于暂停状态。animation_speed
是动画的延迟时间,单位为毫秒。step
是动画中l_ratio和k_ratio的步长。l_ratio
和k_ratio
是用于绘制Spirograph曲线的相对距离参数。num_rot
是旋转的次数。如果定义了USE_GLUT
宏并成功编译并运行了程序,那么它将创建一个名为"Spirograph"的OpenGL图形窗口,并执行以下操作:
glClearColor
函数设置背景颜色为白色。glClear
函数清除颜色缓冲区。spirograph
函数生成Spirograph曲线的坐标数据。glBegin
和glVertex2f
函数绘制线段。glColor3f
函数设置颜色为蓝色,使用glPointSize
函数设置点的大小为2像素。glRasterPos2f
和glutBitmapString
函数在窗口中显示l和k参数的值。glutSwapBuffers
函数交换前后缓冲区。程序还定义了一些其他函数,包括:
timer_cb
是一个GLUT定时器回调函数,用于控制动画的刷新频率。keyboard_cb
是一个键盘事件回调函数,用于监听按键操作。这就是代码的主要内容和功能。它提供了一个完整的Spirograph曲线生成和可视化的功能,并且可以通过键盘操作来控制动画的暂停、调节l和k参数的值等。如果没有OpenGL支持或者不需要图形界面,可以直接调用test
函数进行曲线生成和保存。