개요
이 프로젝트는 2~3주차 동안 이루어진 포켓몬 게임 제작 프로젝트이다.
요구사항
- 각 기능과 구조체는 헤더파일을 이용해 구현해야 함.
- 구조체는 꼭 malloc을 이용해 동적 heap메모리로 사용하여야 함. 구조체 멤버는 상관 X
- 리눅스 환경 gcc컴파일러를 통해서 컴파일 및 실행이 되어야 함
- 추가 점수는 자료구조를 이용할 경우 부여함.
구성
pokemon.txt : 포켓몬 리스트가 들어가 있는 파일
pokemon.h : 포켓몬 게임을 위한 함수가 들어간 헤더파일
pokemon.c : main.c
save.txt : 게임 중간에 저장시 발생하는 파일
pokemon.txt
//pokemon.txt
12
파이리 불 60 80
꼬부기 물 40 120
이상해씨 풀 50 100
브케인 불 50 90
라이코 물 40 120
치코리타 풀 50 100
아차모 불 50 90
물짱이 물 40 120
나무지기 풀 50 100
불꽃숭이 불 50 90
팽도리 물 40 120
모부기 풀 50 101
save.txt
// save.txt
[트레이너 정보]
소유한 포켓몬 수 : 6
지갑 : 1372
몬스터 볼 : 0
물약 개수 : 1
[소유한 포켓몬 리스트]
파이리 fire 불 87 141 0
물짱이 물짱 물 76 132 132
꼬부기 꼬북 물 93 143 143
치코리타 치코 풀 75 140 117
아차모 아차 불 50 90 90
물짱이 물짱 물 47 125 13
메인 함수 코드 구성 로직
- 맨처음 포켓몬 리스트를 읽어온다.
- initial함수 동작(초기화면 출력)
- 이어할지, 새로할지 선택
- play하고 만일 포켓몬 마스터가 되었을 때, 새로할지 말지를 반환값으로 결정
- 게임 내용 세이브
main.c
#include"pokemon.h"
int main() {
srand(time(NULL)); // 랜덤 시드 초기화
Trainer* trainer = (Trainer*)malloc(sizeof(Trainer));
if (trainer == NULL) {
perror("메모리 할당 실패");
exit(EXIT_FAILURE);
}
Node* pokemonList = loadPokemonData();
char a = "";
while(1){
initial();
int gameChoice = open_file();
if (gameChoice == -1) {
selectPokemon(trainer);
}
else if (gameChoice == 1) {
loadGame(trainer);
}
a = play(trainer, pokemonList);
// 입력받은 값이 'N'이 아니면 계속 게임 진행
if (a == 'N' || a == 'n') {
break;
}
// 'Y'가 입력되면 계속 게임 재시작
else {
continue;
}
// 게임을 진행하고 저장하기
}
saveGame(trainer);
// 메모리 해제
free(trainer);
return 0;
}
pokemon.h
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h> // linux는 unistd.h 사용
#define MAX_POKEMON 12 // 100
#define MAX_NAME_LEN 50
typedef struct Pokemonster {
char name[MAX_NAME_LEN];
char type[MAX_NAME_LEN];
char nickname[MAX_NAME_LEN];
int attack;
int hp;
int now_hp;
} Pokemon;
typedef struct Trainer {
Pokemon ownedPokemon[MAX_POKEMON]; //소유한 포켓몬
int pokemonCount;
int wallet;
int pokeballCount;
int potionCount;
} Trainer;
typedef struct Node {
Pokemon data;
struct Node* next;
} Node;
Node *createNode(const Pokemon *data) {
Node *newNode = (Node *)malloc(sizeof(Node));
if (!newNode) {
perror("메모리 할당 실패");
exit(EXIT_FAILURE);
}
newNode->data = *data;
newNode->next = NULL;
return newNode;
}
// 포켓몬 리스트 로드하는 함수
Node *loadPokemonData() {
FILE *file = fopen("pokemon.txt", "r");
if (!file) {
perror("파일을 열 수 없습니다");
exit(EXIT_FAILURE);
}
int count;
fscanf(file, "%d", &count); // 첫 줄에서 포켓몬 개수 읽기
Node *head = NULL, *tail = NULL;
for (int i = 0; i < count; i++) {
Pokemon temp;
fscanf(file, "%s %s %d %d", temp.name, temp.type, &temp.attack, &temp.hp);
Node *newNode = createNode(&temp);
if (head == NULL) {
head = newNode;
tail = newNode;
}
else {
tail->next = newNode;
tail = newNode;
}
}
fclose(file);
return head;
}
// 초기화면 출력
int initial() {
printf("===============================");
printf("\n");
printf(" KnockOn Pokemon Game ");
printf("\n");
printf(" ");
printf("\n");
printf(" press enter to start ");
printf("\n");
printf("===============================\n");
while (getchar() != '\n');
system("clear");
return 0;
}
// 이어할지, 새로할지 선택
int open_file() {
int num = 0;
printf("===============================");
printf("\n");
printf(" 1. 새로하기 2. 이어하기 ");
printf("\n");
printf(">>");
scanf("%d", &num);
system("clear");
FILE* file = fopen("save.txt", "r");
if ((!file) || (num == 1)) {
return -1; // 처음부터 시작
}
else if (num == 2) {
return 1; // 이어하기 처리 필요
}
fclose(file);
}
// 랜덤값을 만들어내는 함수
int randomStat(int baseStat, int range) {
return baseStat + (rand() % (range - baseStat + 1));
}
// wildPokemon을 Trainer의 OwnedPokemon 배열에 추가하는 함수(포켓몬 잡는 함수)
void capturePokemon(Trainer* trainer, Pokemon* wildPokemon) {
// 배열 크기 확인 (예: OwnedPokemon 배열 크기가 6인 경우)
if (trainer->pokemonCount < 6) {
// 소유한 포켓몬 배열에 새 포켓몬 추가
printf("포켓몬의 별명을 입력하세요: ");
scanf("%s", wildPokemon->nickname);
trainer->ownedPokemon[trainer->pokemonCount] = *wildPokemon;
// 포켓몬 카운트 증가
trainer->pokemonCount++;
system("clear");
}
else {
printf("포켓몬 보관함이 가득 찼습니다! 더 이상 포켓몬을 잡을 수 없습니다.\n");
}
}
// 포켓몬 상성 함수
float calculateDamageMultiplier(const char* attackerType, const char* defenderType) {
if ((strcmp(attackerType, "불") == 0 && strcmp(defenderType, "풀") == 0) ||
(strcmp(attackerType, "풀") == 0 && strcmp(defenderType, "물") == 0) ||
(strcmp(attackerType, "물") == 0 && strcmp(defenderType, "불") == 0)) {
printf("효과가 굉장했다!\n");
return 1.5f;
}
if ((strcmp(attackerType, "풀") == 0 && strcmp(defenderType, "불") == 0) ||
(strcmp(attackerType, "불") == 0 && strcmp(defenderType, "물") == 0) ||
(strcmp(attackerType, "물") == 0 && strcmp(defenderType, "풀") == 0)) {
printf("효과가 별로인 듯 하다...\n");
return 0.5f;
}
return 1.0f;
}
// 포켓몬 배틀 상태창 출력함수
void printBattleStatus(Pokemon* myPokemon, Pokemon* wildPokemon) {
printf("==============================\n");
printf("\t\t%s\n\t\t현재 HP: %d / %d\n", wildPokemon->name, wildPokemon->now_hp, wildPokemon->hp);
printf("%s\n현재 HP: %d / %d\n", myPokemon->name, myPokemon->now_hp, myPokemon->hp);
printf("==============================\n");
}
// 트레이너의 가방 함수
int trainer_bag(Pokemon* myPokemon, Pokemon* wildPokemon, Trainer* trainer) {
printf("===============================");
printf("\n");
printf("1. 몬스터볼 x %d", trainer->pokeballCount);
printf("\n");
printf("2. 회복약 x %d", trainer->potionCount);
printf("\n");
printf(">> ");
int choice = 0;
int catch = 100 * (1 - (double)wildPokemon->now_hp / wildPokemon->hp); //포획확률
scanf("%d", &choice);
if (choice == 1) {
if (trainer->pokeballCount >= 1) {
trainer->pokeballCount -= 1;
printBattleStatus(myPokemon, wildPokemon);
printf("===============================");
printf("\n");
printf("몬스터볼을 %s에게 던졌다!", wildPokemon);
printf("...\n...\n");
if (rand() % 100 < catch) { // catch는 포획확률이고, 랜덤값이 이 범위에 있다면, 해당 포획확률%로 포획
printf("%s을(를) 잡았다!\n", wildPokemon->name);
capturePokemon(trainer, wildPokemon);
return 0;
}
else {
printf("포켓몬이 몬스터볼에서 빠져나왔다!\n");
return 1;
}
}
else {
printf("몬스터볼이 없다...!\n");
}
}
else if (choice == 2) {
if (trainer->potionCount >= 1) {
trainer->potionCount -= 1;
if (myPokemon->now_hp < myPokemon->hp) {
myPokemon->now_hp += (double)myPokemon->hp*0.3;
printf("===============================");
printf("\n");
printf("회복약을 사용하였다!\n");
printf("%s(은)는 %.0f의 체력을 회복하였다.\n", myPokemon->name, (double)myPokemon->hp*0.3);
if (myPokemon->now_hp > myPokemon->hp) {
myPokemon->now_hp = myPokemon->hp;
printBattleStatus(myPokemon, wildPokemon);
}
}
else {
printf("회복할 체력이 없다...!\n");
printBattleStatus(myPokemon, wildPokemon);
}
}
else {
printf("회복약이 없다...!\n");
}
}
}
// 포켓몬 교체함수
Pokemon* changePokemon(Pokemon* currentPokemon, Pokemon* wildPokemon, Trainer* trainer) {
printf("사용할 포켓몬을 선택하세요:\n");
for (int i = 0; i < trainer->pokemonCount; i++) {
if (trainer->ownedPokemon[i].now_hp > 0) {
printf("%d. %s (HP: %d/%d)\n", i + 1, trainer->ownedPokemon[i].name, trainer->ownedPokemon[i].now_hp, trainer->ownedPokemon[i].hp);
}
}
int choice;
do {
printf(">> ");
scanf("%d", &choice);
choice--; // 배열 인덱스는 0부터 시작
if (choice < 0 || choice >= trainer->pokemonCount || trainer->ownedPokemon[choice].now_hp <= 0) {
printf("잘못된 선택입니다. 다시 선택하세요.\n");
}
} while (choice < 0 || choice >= trainer->pokemonCount || trainer->ownedPokemon[choice].now_hp <= 0);
currentPokemon = &trainer->ownedPokemon[choice];
printf("'%s'를 전투에 내보냈다!\n", currentPokemon->name);
printBattleStatus(currentPokemon, wildPokemon);
return currentPokemon;
}
// 배틀함수
void battle(Pokemon* myPokemon, Pokemon* wildPokemon, Trainer* trainer) {
while (myPokemon->now_hp > 0 && wildPokemon->now_hp > 0) {
printf("무엇을 해야할까?\n1. 공격 2. 가방열기 3. 도망치기\n>> ");
int choice;
scanf("%d", &choice);
system("clear");
if (choice == 2) {
printBattleStatus(myPokemon, wildPokemon);
int i = trainer_bag(myPokemon, wildPokemon, trainer);
if (i == 0) { return; }
else {
printBattleStatus(myPokemon, wildPokemon);
continue;
}
}
else if (choice == 3) {
if (rand() % 100 < (110 * (1 - (double)wildPokemon->now_hp / wildPokemon->hp))) {
printf("성공적으로 도망쳤다!\n");
return;
}
else {
printf("지금 도망치긴 힘들 것 같다...\n");
printBattleStatus(myPokemon, wildPokemon);
continue;
}
}
int first = rand() % 2; // 누가 먼저 공격할지 랜덤
for (int i = 0; i < 2; i++) {
if (myPokemon->now_hp <= 0 || wildPokemon->now_hp <= 0)
break;
Pokemon* attacker = first == i ? myPokemon : wildPokemon;
Pokemon* defender = first == i ? wildPokemon : myPokemon;
float multiplier = calculateDamageMultiplier(attacker->type, defender->type);
int damage = (int)(attacker->attack * multiplier);
// 크리티컬 확률 20%
if (rand() % 100 < 20) {
damage = (int)(damage * 1.5);
printf("급소에 맞았다!\n");
}
defender->now_hp -= damage;
if (defender->now_hp < 0) defender->now_hp = 0;
printf("%s가 %d의 데미지를 입혔다!\n", attacker->name, damage);
printBattleStatus(myPokemon, wildPokemon);
}
}
// 전투 종료 조건
if (wildPokemon->now_hp <= 0) {
int reward = randomStat(300, 500);
trainer->wallet += reward;
printf("야생의 %s를 물리쳤다! %d원을 얻었다!\n", wildPokemon->name, reward);
}
else if (myPokemon->now_hp <= 0) {
printf("%s가 기절했다...!\n", myPokemon->name);
if (trainer->pokemonCount <= 1) {
if (trainer->wallet >= 1000) {
printf("눈앞이 깜깜해졌다... 1000원을 잃었다.\n");
trainer->wallet -= 1000;
}
else {
printf("눈앞이 깜깜해졌다... 남은 돈을 다 잃었다.\n");
trainer->wallet = 0;
}
}
else if (trainer->pokemonCount > 1) {
int num = 0; //포켓몬들의 체력 합
for (int i = 0; i < trainer->pokemonCount; i++) {
num += trainer->ownedPokemon[i].now_hp;
}
if (num >= 1) {
Pokemon* nextPokemon = changePokemon(myPokemon, wildPokemon, trainer); // 포켓몬 교체
// 교체 후 다시 battle() 호출
battle(nextPokemon, wildPokemon, trainer); // 포켓몬 교체 후 전투 재개
return; // 다시 battle()이 호출되었으므로 원래의 battle() 실행 흐름 종료
}
else if (num == 0) {
if (trainer->wallet >= 1000) {
printf("눈앞이 깜깜해졌다... 1000원을 잃었다.\n");
trainer->wallet -= 1000;
}
else {
printf("눈앞이 깜깜해졌다... 남은 돈을 다 잃었다.\n");
trainer->wallet = 0;
}
}
}
}
}
// 배틀전 함수
void adventure(Trainer* trainer, Node* pokemonList) {
//sleep(randomStat(1, 5)); // 1~5초 대기
// 랜덤한 야생 포켓몬 등장
Node* current = pokemonList;
int index = rand() % MAX_POKEMON;
while (index-- && current->next)
current = current->next;
Pokemon wildPokemon = current->data;
wildPokemon.attack = randomStat(wildPokemon.attack, 100);
wildPokemon.hp = randomStat(wildPokemon.hp, 150);
wildPokemon.now_hp = wildPokemon.hp;
printf("앗! 야생의 %s가 나타났다!\n", wildPokemon.name);
printBattleStatus(&trainer->ownedPokemon[0], &wildPokemon);
// 전투 시작
battle(&trainer->ownedPokemon[0], &wildPokemon, trainer); //battle의 매개변수 myPokemon 포인터가 trainer의 포켓몬 구조체를 가리키기위해 주소 전달
}
// 포켓몬을 리스트에서 내가 선택한 포켓몬을 찾는 함수인데... 중간에 만들면서 필요가 없어졌다.. 왜 넣었지...
void findpokemon(Node* guide, const char* selectedPokemon, int* attack, int* hp) {
Node* head = guide;
while (head != NULL) {
if (strcmp(head->data.name, selectedPokemon) == 0) {
*attack = head->data.attack;
*hp = head->data.hp;
return;
}
head = head->next;
}
}
// 초기 포켓몬 선택함수
void selectPokemon(Trainer* trainer) {
printf("===============================");
printf("\n");
printf("어느 포켓몬을 선택하시겠습니까?");
printf("\n");
printf("1. 파이리 2. 이상해씨 3. 꼬부기");
printf("\n");
printf(">>");
int choice = 0;
scanf("%d", &choice);
system("clear");
Pokemon selectedPokemon;
switch (choice) {
case 1:
strcpy(selectedPokemon.name, "파이리");
strcpy(selectedPokemon.type, "불");
selectedPokemon.attack = randomStat(60, 100);
selectedPokemon.hp = randomStat(80, 150);
selectedPokemon.now_hp = selectedPokemon.hp;
break;
case 2:
strcpy(selectedPokemon.name, "이상해씨");
strcpy(selectedPokemon.type, "풀");
selectedPokemon.attack = randomStat(50, 100);
selectedPokemon.hp = randomStat(100, 150);
selectedPokemon.now_hp = selectedPokemon.hp;
break;
case 3:
strcpy(selectedPokemon.name, "꼬부기");
strcpy(selectedPokemon.type, "물");
selectedPokemon.attack = randomStat(40, 100);
selectedPokemon.hp = randomStat(120, 150);
selectedPokemon.now_hp = selectedPokemon.hp;
break;
default:
printf("선택지에 없는 포켓몬입니다. 다시 선택해주세요.\n");
return;
}
printf("포켓몬의 별명을 입력하세요: ");
scanf("%s", selectedPokemon.nickname);
trainer->pokemonCount = 1;
trainer->wallet = 10000;
trainer->pokeballCount = 0;
trainer->potionCount = 0;
trainer->ownedPokemon[0] = selectedPokemon;
system("clear");
printf("포켓몬 선택 완료! %s(으)로 모험을 시작합니다.\n", trainer->ownedPokemon[0].name);
printf("현재 지갑: %d원\n", trainer->wallet);
}
// 게임 세이브 함수
void saveGame(const Trainer* trainer) {
FILE* file = fopen("save.txt", "w");
if (!file) {
perror("파일을 열 수 없습니다");
exit(EXIT_FAILURE);
}
fprintf(file, "[트레이너 정보]\n");
fprintf(file, "소유한 포켓몬 수 : %d\n", trainer->pokemonCount);
fprintf(file, "지갑 : %d\n", trainer->wallet);
fprintf(file, "몬스터 볼 : %d\n", trainer->pokeballCount);
fprintf(file, "물약 개수 : %d\n", trainer->potionCount);
fprintf(file, "[소유한 포켓몬 리스트]\n");
for (int i = 0; i < trainer->pokemonCount; i++) {
fprintf(file, "%s %s %s %d %d %d\n",
trainer->ownedPokemon[i].name,
trainer->ownedPokemon[i].nickname,
trainer->ownedPokemon[i].type,
trainer->ownedPokemon[i].attack,
trainer->ownedPokemon[i].hp,
trainer->ownedPokemon[i].now_hp);
}
fclose(file);
}
// 게임 로드함수
void loadGame(Trainer* trainer) {
FILE* file = fopen("save.txt", "r");
if (!file) {
perror("파일을 열 수 없습니다");
exit(EXIT_FAILURE);
}
//트레이너 정보 읽기
fscanf(file, "[트레이너 정보]\n");
fscanf(file, "소유한 포켓몬 수 : %d\n", &trainer->pokemonCount);
fscanf(file, "지갑 : %d\n", &trainer->wallet);
fscanf(file, "몬스터 볼 : %d\n", &trainer->pokeballCount);
fscanf(file, "물약 개수 : %d\n", &trainer->potionCount);
fscanf(file, "[소유한 포켓몬 리스트]\n");
for (int i = 0; i < trainer->pokemonCount; i++) {
fscanf(file, "%s %s %s %d %d %d %d\n",
trainer->ownedPokemon[i].name,
trainer->ownedPokemon[i].nickname,
trainer->ownedPokemon[i].type,
&trainer->ownedPokemon[i].attack,
&trainer->ownedPokemon[i].hp,
&trainer->ownedPokemon[i].now_hp);
}
fclose(file);
printf("이어서 게임을 시작합니다. %s(으)로 모험을 시작합니다.\n", trainer->ownedPokemon[0].name);
printf("현재 지갑: %d원\n", trainer->wallet);
}
// 상점 함수
void shop(Trainer* trainer) {
int choice, quantity;
int pokeballPrice = 1000;
int potionPrice = 500;
while (1) {
printf("===============================\n");
printf("상점 지갑 : %d원\n", trainer->wallet);
printf("1. 포켓몬볼 (%d원)\n", pokeballPrice);
printf("2. 상처약 (%d원)\n", potionPrice);
printf("0. 나가기\n");
printf("===============================\n");
printf("무엇을 구매할까요? >> ");
scanf("%d", &choice);
if (choice == 0) break;
printf("몇 개를 구매할까요? >> ");
scanf("%d", &quantity);
if (choice == 1 && trainer->wallet >= pokeballPrice * quantity) {
trainer->wallet -= pokeballPrice * quantity;
printf("포켓몬볼 %d개를 구매했습니다!\n", quantity);
trainer->pokeballCount = quantity;
}
else if (choice == 2 && trainer->wallet >= potionPrice * quantity) {
trainer->wallet -= potionPrice * quantity;
printf("상처약 %d개를 구매했습니다!\n", quantity);
trainer->potionCount = quantity;
}
else {
printf("돈이 부족하거나 잘못된 입력입니다!\n");
}
}
system("clear");
}
// 포켓몬 센터 함수
void pokemonCenter(Trainer* trainer) {
printf("===============================\n");
printf("모든 포켓몬을 치료하는 중입니다 ... ... ...\n");
for (int i = 0; i < trainer->pokemonCount; i++) {
trainer->ownedPokemon[i].now_hp = trainer->ownedPokemon[i].hp; // HP를 다시 원래대로 설정
}
printf("포켓몬의 체력이 완전히 회복되었습니다!\n");
printf("===============================\n");
while (getchar() != '\n');
}
// 포켓몬 도감 출력함수
void pokedex(Node* pokemonList) {
printf("===============================\n");
printf("포켓몬 도감\n");
printf("이름 | 속성 | 공격력 | 체력\n");
printf("------------------------------------\n");
Node* current = pokemonList;
while (current != NULL) {
printf("%-10s | %-8s | %-7d | %-5d\n",
current->data.name,
current->data.type,
current->data.attack,
current->data.hp);
current = current->next;
}
printf("===============================\n");
}
// 게임의 전반적인 플레이를 할 수 있게 설계된 분기 함수
char play(Trainer* trainer, Node* pokemonList) {
int num = 0;
int i = 0;
char a = "";
while (1) {
if (trainer->pokemonCount == 6) {
printf("===============================\n");
printf("포켓몬 마스터가 되었다!\n");
for (int i = 0; i < 6; i++) {
printf("%s %d/%d\n", trainer->ownedPokemon[i].nickname, trainer->ownedPokemon[i].now_hp, trainer->ownedPokemon[i].hp);
}
printf("포켓몬 볼 x %d\n", trainer->pokeballCount);
printf("회복약 x %d\n", trainer->potionCount);
printf("지갑 %d원\n", trainer->wallet);
printf("===============================\n");
printf("게임을 재시작하겠습니까? (Y/N)\n");
printf(">> ");
getchar(); //getchar로 엔터 버퍼지우기
scanf("%c", &a);
if (a == 'Y' || a == 'y') {
return a; // Y 입력시 'Y' 반환하여 게임 재시작
}
else if (a == 'N' || a == 'n') {
return a; // N 입력시 'N' 반환하여 게임 종료
}
else {
printf("잘못된 입력입니다. 다시 입력해주세요.\n");
continue; // 잘못된 입력일 경우 계속해서 묻기
}
}
printf("===============================\n");
printf("모험을 진행하시겠습니까?\n");
printf("1. 네 2. 저장 3. 상점 4. 포켓몬센터 5. 포켓몬 도감 6. 종료\n");
printf(">> ");
scanf("%d", &num);
system("clear");
switch (num) {
case 1:
printf("===============================\n");
if (trainer->ownedPokemon[0].now_hp == 0) {
printf("포켓몬센터부터 가자...\n");
pokemonCenter(trainer);
break;
}
printf("===============================\n");
printf("포켓몬을 탐색하는 중...\n");
adventure(trainer, pokemonList);
break;
case 2:
saveGame(trainer);
printf("===============================\n");
printf("게임이 저장되었습니다!\n");
break;
case 3:
shop(trainer);
break;
case 4:
pokemonCenter(trainer);
break;
case 5:
pokedex(pokemonList);
break;
case 6:
printf("===============================\n");
printf("게임을 종료합니다.\n");
return 'n';
default:
printf("===============================\n");
printf("잘못된 입력입니다! 다시 선택해주세요.\n");
}
}
}
결과
코드리뷰는 지속적으로 최신화해서 포스트 수정할 예정이다.
아래 파일은 게임 파일이다.
pokemon
0.03MB
끝.
'STUDY' 카테고리의 다른 글
[3주차 TIL] KnockOn Bootcamp Pre.Rev : ELF 파일 구조 (3) | 2024.12.22 |
---|---|
[3주차 TIL] KnockOn Bootcamp Pre.Rev : 메모리 구조와 매핑 (0) | 2024.12.21 |
[3주차 TIL] KnockOn Bootcamp Pre.Rev : 링커와 로더 (0) | 2024.12.21 |
[3주차 TIL] KnockOn Bootcamp Pre.Rev : 컴파일러 (0) | 2024.12.21 |
[3주차 TIL] KnockOn Bootcamp Pre.Rev : 아키텍쳐 (0) | 2024.12.21 |