├── answer ├── csapp.h ├── log.h ├── wrappers.h ├── minesweeper_helpers.h ├── template.cpp ├── just_open_many_channels.cpp ├── naive.cpp ├── naive_optim.cpp ├── naive_mt.cpp ├── interact.cpp ├── simple_expand_single_thread.cpp ├── expand_with_queue.cpp └── expand_with_queue_mt.cpp ├── lib ├── common.cpp ├── futex.h ├── common.h ├── queue.cpp ├── queue.h ├── log.h ├── futex.cpp ├── shm.cpp ├── log.cpp ├── minesweeper_helpers.h ├── shm.h ├── wrappers.cpp ├── wrappers.h ├── minesweeper_helpers.cpp ├── csapp.h └── csapp.cpp ├── Makefile_handout.mk ├── generate_example_maps.py ├── .gitignore ├── LICENSE ├── README.md ├── Makefile ├── map_visualizer.cpp ├── blank_counter.cpp ├── map_generator.cpp ├── statement ├── zh-cn.md └── en-us.md ├── judger.cpp └── game_server.cpp /answer/csapp.h: -------------------------------------------------------------------------------- 1 | ../lib/csapp.h -------------------------------------------------------------------------------- /answer/log.h: -------------------------------------------------------------------------------- 1 | ../lib/log.h -------------------------------------------------------------------------------- /answer/wrappers.h: -------------------------------------------------------------------------------- 1 | ../lib/wrappers.h -------------------------------------------------------------------------------- /answer/minesweeper_helpers.h: -------------------------------------------------------------------------------- 1 | ../lib/minesweeper_helpers.h -------------------------------------------------------------------------------- /lib/common.cpp: -------------------------------------------------------------------------------- 1 | #include "wrappers.h" 2 | #include "common.h" 3 | 4 | void exit_when_parent_dies() { 5 | Prctl(PR_SET_PDEATHSIG, SIGKILL); 6 | if (getppid() == 1) exit(0); 7 | } -------------------------------------------------------------------------------- /answer/template.cpp: -------------------------------------------------------------------------------- 1 | // 请在程序开头引用此头文件 2 | #include "minesweeper_helpers.h" 3 | 4 | // N: 地图的边长 5 | // K: 地图中的雷的数量 6 | long N, K; 7 | int constant_A; 8 | 9 | int main() { 10 | // 请在程序开始时调用此函数来初始化 & 获得 N 和 K 的值 11 | minesweeper_init(N, K, constant_A); 12 | 13 | // 写代码吧!加油! 14 | 15 | return 0; 16 | } -------------------------------------------------------------------------------- /lib/futex.h: -------------------------------------------------------------------------------- 1 | /* 2 | futex.h - Functions (wrappers) for manuplating futexes 3 | */ 4 | 5 | #ifndef __MINESWEEPER_FUTEX_H__ 6 | #define __MINESWEEPER_FUTEX_H__ 7 | 8 | int futex_wait(uint32_t *futex_ptr, uint32_t old_val); 9 | 10 | int futex_wake(uint32_t *futex_ptr); 11 | 12 | #endif // __MINESWEEPER_FUTEX_H__ -------------------------------------------------------------------------------- /lib/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | common.h - Common functions used by player's program, the game server and 3 | the judger. 4 | */ 5 | #ifndef __MINESWEEPER_COMMON_H__ 6 | #define __MINESWEEPER_COMMON_H__ 7 | 8 | // Let the process exits when its parent dies 9 | void exit_when_parent_dies(); 10 | 11 | constexpr int MAX_OPEN_GRID = 16384; 12 | 13 | #endif // __MINESWEEPER_COMMON_H__ -------------------------------------------------------------------------------- /lib/queue.cpp: -------------------------------------------------------------------------------- 1 | #include "queue.h" 2 | 3 | void Queue::clear() { 4 | head = 0; 5 | tail = -1; 6 | } 7 | 8 | void Queue::push(int r, int c) { 9 | tail += 1; 10 | q[tail][0] = r; 11 | q[tail][1] = c; 12 | } 13 | 14 | void Queue::pop(int &r, int &c) { 15 | r = q[head][0]; 16 | c = q[head][1]; 17 | head += 1; 18 | } 19 | 20 | bool Queue::empty() { 21 | return head > tail; 22 | } 23 | -------------------------------------------------------------------------------- /lib/queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | queue.h - Queue for BFS 3 | */ 4 | 5 | #ifndef __MINESWEEPER_QUEUE_H__ 6 | #define __MINESWEEPER_QUEUE_H__ 7 | 8 | #include "common.h" 9 | 10 | struct Queue { 11 | int q[MAX_OPEN_GRID+16][2]; 12 | int head, tail; 13 | 14 | void clear(); 15 | void push(int r, int c); 16 | void pop(int &r, int &c); 17 | bool empty(); 18 | }; 19 | 20 | #endif // __MINESWEEPER_QUEUE_H__ -------------------------------------------------------------------------------- /lib/log.h: -------------------------------------------------------------------------------- 1 | /* 2 | log.h - various logging functions 3 | */ 4 | #ifndef __MINESWEEPER_LOG_H__ 5 | #define __MINESWEEPER_LOG_H__ 6 | 7 | #include "csapp.h" // for "extern const char* prog_name" 8 | 9 | // Log something to stderr. The `[$prog_name]` is added automatically as a prefix 10 | void log(const char* format, ...); 11 | 12 | // Log something to stderr. Use the syscall `write` directly. Suitable for signal handlers 13 | void sio_log(const char* msg); 14 | 15 | // A string containing "This is a bug. Please contact ..." 16 | extern const char this_is_a_bug_str[]; 17 | 18 | #endif // __MINESWEEPER_LOG_H__ -------------------------------------------------------------------------------- /answer/just_open_many_channels.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | just_open_many_channels.cpp - Just open many channels, and perform `click(0, 0, 0)` 3 | on each channel. For stability testing. 4 | */ 5 | 6 | #include 7 | 8 | // 请在程序开头引用此头文件 9 | #include "minesweeper_helpers.h" 10 | 11 | constexpr int MAXN = 65536; 12 | 13 | // N: 地图的边长 14 | // K: 地图中的雷的数量 15 | long N, K; 16 | int constant_A; 17 | 18 | int main() { 19 | // 请在程序开始时调用此函数来初始化 & 获得 N 和 K 的值 20 | minesweeper_init(N, K, constant_A); 21 | 22 | for (int i = 0; i < 1024; ++i) { 23 | Channel channel = create_channel(); 24 | channel.click(0, 0, 0); 25 | } 26 | 27 | return 0; 28 | } -------------------------------------------------------------------------------- /Makefile_handout.mk: -------------------------------------------------------------------------------- 1 | .SECONDEXPANSION: 2 | 3 | CC = g++ 4 | CXXFLAGS ?= -g -Ofast -std=c++17 -Wall -march=native -Wl,--as-needed -lpthread -lrt -pthread 5 | 6 | LIBS = csapp wrappers minesweeper_helpers log common shm futex queue 7 | EXES = judger game_server map_generator map_visualizer naive naive_optim interact answer 8 | 9 | LIB_OBJS = $(foreach x, $(LIBS), $(addsuffix .o, $(x))) 10 | 11 | .PHONY: all clean 12 | 13 | all: $(EXES) 14 | 15 | %.o: %.cpp # for executables 16 | $(CC) -c $(CXXFLAGS) $< -o $@ 17 | 18 | %.o: lib/%.cpp lib/%.h # for lib/?.cpp 19 | $(CC) -c $(CXXFLAGS) $< -o $@ 20 | 21 | $(EXES): $$@.o $(LIB_OBJS) 22 | $(CC) $@.o $(LIB_OBJS) $(CXXFLAGS) -o $@ 23 | 24 | clean: 25 | rm -rf *.o $(EXES) -------------------------------------------------------------------------------- /generate_example_maps.py: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | import shutil, os 3 | 4 | # Check whether "map_generator" is present 5 | if not os.path.exists('map_generator'): 6 | print('Error: `map_generator` does not exists. Maybe you should run `make` first?') 7 | 8 | # Create the directory 9 | if os.path.exists('map'): 10 | shutil.rmtree('map', ignore_errors=True) 11 | os.mkdir('map') 12 | 13 | sizes = [2**N for N in range(4, 16+1)] 14 | seed = 0 15 | 16 | for N in sizes: 17 | K = N*N//8 18 | print(f"Generating map with N={N}, K={K}, seed={seed}") 19 | filename = "%d_%d_%d.map" % (N, K, seed) 20 | exitcode = os.system(f"./map_generator {N} {K} {seed} > map/{filename}") 21 | if exitcode != 0: 22 | print(f"The generator terminated with a non-zero exitcode: {exitcode}") -------------------------------------------------------------------------------- /lib/futex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "wrappers.h" 4 | #include "futex.h" 5 | #include "log.h" 6 | 7 | static int futex(uint32_t *uaddr, int futex_op, uint32_t val, 8 | const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3) { 9 | return syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr, val3); 10 | } 11 | 12 | int futex_wait(uint32_t *futex_ptr, uint32_t old_val) { 13 | int rc = futex(futex_ptr, FUTEX_WAIT, old_val, NULL, NULL, 0); 14 | if (rc == -1 && errno != EAGAIN) { 15 | unix_error("futex_wait error"); 16 | } 17 | return rc; 18 | } 19 | 20 | int futex_wake(uint32_t *futex_ptr) { 21 | int rc = futex(futex_ptr, FUTEX_WAKE, 1, NULL, NULL, 0); 22 | if (rc == -1) { 23 | unix_error("futex_wake error"); 24 | } 25 | return rc; 26 | } -------------------------------------------------------------------------------- /lib/shm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "wrappers.h" 5 | #include "common.h" 6 | #include "log.h" 7 | #include "shm.h" 8 | 9 | char* open_shm(const char* shm_name) { 10 | // Open the shm region 11 | int mem_fd = Shm_open(shm_name, O_CREAT | O_RDWR, S_IRWXU); 12 | // Mmap-ing 13 | char* ptr = (char*)Mmap(NULL, TOTAL_SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, 0); 14 | return ptr; 15 | } 16 | 17 | void init_shm_region(char* pos) { 18 | SHM_PENDING_BIT(pos) = 0; 19 | SHM_SLEEPING_BIT(pos) = 0; 20 | SHM_DONE_BIT(pos) = 0; 21 | } 22 | 23 | void generate_random_shm_name(char* result) { 24 | static std::mt19937_64 rng(std::chrono::high_resolution_clock::now().time_since_epoch().count()); 25 | rng(); rng(); 26 | unsigned long long shm_tag = rng(); 27 | sprintf(result, "/minesweeper_shm_%llu", shm_tag); 28 | } -------------------------------------------------------------------------------- /lib/log.cpp: -------------------------------------------------------------------------------- 1 | #include "wrappers.h" 2 | #include "log.h" 3 | 4 | extern const char* prog_name; 5 | 6 | static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; 7 | 8 | void log(const char* format, ...) { 9 | va_list arglist; 10 | va_start(arglist, format); 11 | Pthread_mutex_lock(&log_mutex); 12 | if (prog_name) 13 | fprintf(stderr, "[%s] ", prog_name); 14 | vfprintf(stderr, format, arglist); 15 | Pthread_mutex_unlock(&log_mutex); 16 | va_end(arglist); 17 | } 18 | 19 | void sio_log(const char* msg) { 20 | const char buf[] = "[] "; 21 | Write(STDERR_FILENO, buf, 1); 22 | Write(STDERR_FILENO, prog_name, sio_strlen(prog_name)); 23 | Write(STDERR_FILENO, buf+1, 2); 24 | Write(STDERR_FILENO, msg, sio_strlen(msg)); 25 | } 26 | 27 | const char this_is_a_bug_str[] = "This is a bug. Please contact interestingLSY (interestingLSY@gmail.com). \ 28 | Thank you!\n"; 29 | -------------------------------------------------------------------------------- /answer/naive.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | naive.cpp - A naive solution 3 | This solution just clicks all grids one by one 4 | */ 5 | 6 | #include 7 | 8 | // 请在程序开头引用此头文件 9 | #include "minesweeper_helpers.h" 10 | 11 | constexpr int MAXN = 65536; 12 | 13 | // N: 地图的边长 14 | // K: 地图中的雷的数量 15 | long N, K; 16 | int constant_A; 17 | 18 | int main() { 19 | // 请在程序开始时调用此函数来初始化 & 获得 N 和 K 的值 20 | minesweeper_init(N, K, constant_A); 21 | 22 | // 使用这个函数来创建一个 Channel 23 | Channel channel = create_channel(); 24 | 25 | // 依次点击 N*N 地图中的每个格子。 26 | for (long r = 0; r < N; ++r) { 27 | for (long c = 0; c < N; ++c) { 28 | // 点开 (r, c) 这个位置的格子 29 | // 由于我们只是简单地点开所有格子,所以我们令 skip_when_reopen = true 来 30 | // 节约一些时间(Recap: channel.click 的复杂度与点开的格子数量成正比) 31 | ClickResult result = channel.click(r, c, true); 32 | if (result.is_skipped) { 33 | // 我们之前(直接地或间接地)点开过这个方格 34 | } else if (result.is_mine) { 35 | // 踩到地雷了 36 | } else { 37 | // 没踩到地雷 38 | } 39 | } 40 | } 41 | 42 | return 0; 43 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # intlsy's test files 5 | temp.txt 6 | tmp.txt 7 | intlsy-test.cpp 8 | temp.cpp 9 | intlsy-test/ 10 | 11 | # executables 12 | game_server 13 | judger 14 | map_generator 15 | map_visualizer 16 | blank_counter 17 | template 18 | naive 19 | naive_mt 20 | naive_optim 21 | just_open_many_channels 22 | interact 23 | expand_with_queue 24 | expand_with_queue_mt 25 | simple_expand_single_thread 26 | 27 | # maps & handout 28 | map/ 29 | handout/ 30 | minesweeper_handout/ 31 | minesweeper_handout.zip 32 | 33 | # output file of gperf 34 | gmon.out 35 | 36 | # ---> C++ 37 | # Prerequisites 38 | *.d 39 | 40 | # Compiled Object files 41 | *.slo 42 | *.lo 43 | *.o 44 | *.obj 45 | 46 | # Precompiled Headers 47 | *.gch 48 | *.pch 49 | 50 | # Compiled Dynamic libraries 51 | *.so 52 | *.dylib 53 | *.dll 54 | 55 | # Fortran module files 56 | *.mod 57 | *.smod 58 | 59 | # Compiled Static libraries 60 | *.lai 61 | *.la 62 | *.a 63 | *.lib 64 | 65 | # Executables 66 | *.exe 67 | *.out 68 | *.app 69 | 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Shengyu Liu (interestingLSY) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /answer/naive_optim.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | naive_optim.cpp - A naive solution 3 | This solution just clicks all grids one by one, with a simple optimization, 4 | which terminates the program (do not click further) when we detect that 5 | we have opened all non-mine grids. 6 | */ 7 | 8 | #include 9 | 10 | // 请在程序开头引用此头文件 11 | #include "minesweeper_helpers.h" 12 | 13 | constexpr int MAXN = 65536; 14 | 15 | // N: 地图的边长 16 | // K: 地图中的雷的数量 17 | long N, K; 18 | int constant_A; 19 | 20 | int main() { 21 | // 请在程序开始时调用此函数来初始化 & 获得 N 和 K 的值 22 | minesweeper_init(N, K, constant_A); 23 | 24 | // 使用这个函数来创建一个 Channel 25 | Channel channel = create_channel(); 26 | 27 | // 依次点击 N*N 地图中的每个格子。 28 | // 顺便记录一下我们点开的非雷格子数量。如果这个数量达到了 N*N-K,那么我们提前退出。 29 | long open_non_mine = 0; 30 | for (long r = 0; r < N; ++r) { 31 | for (long c = 0; c < N; ++c) { 32 | // 点开 (r, c) 这个位置的格子,并且不要执行间接点开 33 | ClickResult result = channel.click_do_not_expand(r, c); 34 | if (result.is_mine) { 35 | // 踩到地雷了 36 | } else { 37 | // 没踩到地雷 38 | open_non_mine += 1; 39 | if (open_non_mine == N*N-K) { 40 | // 所有的非雷格子都被点开了,傻逼才继续点 41 | goto loop_end; 42 | } 43 | } 44 | } 45 | } 46 | 47 | loop_end:; 48 | 49 | return 0; 50 | } -------------------------------------------------------------------------------- /lib/minesweeper_helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef __MINESWEEPER_HELPERS_H__ 2 | #define __MINESWEEPER_HELPERS_H__ 3 | 4 | #ifndef MINESWEEPER_BIND_CORE_MODE 5 | #define MINESWEEPER_BIND_CORE_MODE 1 // Default: Bind thread to core 6 | #endif 7 | 8 | // ClickResult - 某一次点击的结果 9 | struct ClickResult { 10 | // 点击的方格是不是地雷 11 | bool is_mine; 12 | 13 | // 是否因为“之前点击过这个格子”而被跳过 14 | // Recap: 如果在调用 click 时指定了 skip_when_reopen = true,并且这个方格之前被点过(包括 15 | // 被本线程点过或被其他线程点过),那么为了节省时间,game server 会直接返回“跳过!” 16 | // 此时 is_skipped 会被置为 true。 17 | bool is_skipped; 18 | 19 | // 如果点击的方格不是地雷,而且没有被 skipped,那么这个变量代表有多少个方格被点开 20 | // 别忘了,如果你点的方格中的数字是零,那么它周围的方格也会被点开,并且这个过程可以递归 21 | // 别忘了*2,每次“点开”一个包含数字 0 的格子时,你的程序会收到点开的格子所在的连通块中的 22 | // 所有数字为 0 的格子以及边缘那些数字不为 0 的格子,哪怕这些格子已经被点开过。 23 | int open_grid_count; 24 | 25 | // 一个长度为 open_grid_count 的数组 26 | // (*open_grid_pos)[i][0] 代表第 i 个被点开的格子所在的行 27 | // (*open_grid_pos)[i][1] 代表第 i 个被点开的格子所在的列 28 | // (*open_grid_pos)[i][2] 代表第 i 个被点开的格子中的数字 29 | unsigned short (*open_grid_pos)[16384][3]; 30 | }; 31 | 32 | // Channel - 选手程序和 game server 间相互通信的信道 33 | class Channel { 34 | private: 35 | // Channel ID 36 | int id; 37 | 38 | char* shm_pos; 39 | public: 40 | ClickResult click(long r, long c, bool skip_when_reopen); 41 | ClickResult click_do_not_expand(long r, long c); 42 | friend Channel create_channel(void); 43 | }; 44 | 45 | // 整个程序的初始化。 46 | // This should be called once and only once in the player's program 47 | void minesweeper_init(long &N, long &K, int &constant_A); 48 | void minesweeper_init(int &N, int &K, int &constant_A); 49 | 50 | // 创建一个新的信道 51 | Channel create_channel(void); 52 | 53 | #endif // __MINESWEEPER_HELPERS_H__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HPCGame.Minesweeper 2 | 3 | ## Overview 4 | 5 | This repository contains code for the problem "Minesweeper" (Chinese name: "走,我们扫雷去") in the competition Peking University High Performance Computing Game 0th (Chinese name: "第零届北京大学高性能计算综合能力竞赛"). 6 | 7 | This problem, "Minesweeper", is inspired by the game Minesweeper under the Windows system. In our problem, The player is asked to write a program, simulating the action of a real world minesweeper player. The program can click on a certain square via a API and get the result. For more detail, please refer to the [problem statement](#problem_statement) 8 | 9 | ## Problem Statement 10 | 11 | We provide statements in both Chinese and English. [Chinese Version](statement/zh-cn.md); [English Version](statement/en-us.md). 12 | 13 | ## About This Repository 14 | 15 | This repository contains everything related to the minesweeper problem. 16 | 17 | It contains: 18 | 19 | - The judger `judger.cpp`. 20 | - The game server `game_server.cpp`. It is responsible for interacting with the player's program. 21 | - The standard solution `answer/expand_with_queue_mt.cpp`. 22 | - Some naive & imperfect solutions. They are under the `answer/` diirectory. 23 | - The data generator `map_generator.cpp`. 24 | - The map visualizer `map_visualizer.cpp`. 25 | - Problem statements. [Chinese Version](statement/zh-cn.md); [English Version](statement/en-us.md). 26 | - Some other stuff, like `Makefile`. 27 | 28 | For technical & implementation detail, please refer to comments in those source files. 29 | 30 | ## Build 31 | 32 | Just clone this repository, and execute `make`. 33 | 34 | You can also use `make handout` to generate `minesweeper_handout.zip`. -------------------------------------------------------------------------------- /lib/shm.h: -------------------------------------------------------------------------------- 1 | /* 2 | shm.h - Utilities for manuplating the shm (shared memory) 3 | */ 4 | #ifndef __MINESWEEPER_SHM_H__ 5 | #define __MINESWEEPER_SHM_H__ 6 | 7 | // The maximum number of channels the player's program can have 8 | #define MAX_CHANNEL 1024 9 | 10 | // The size of each shared memory region held by a channel, in bytes 11 | #define CHANNEL_SHM_SIZE (256*1024) // 256 KB 12 | 13 | // The size of the shm region 14 | #define TOTAL_SHM_SIZE (MAX_CHANNEL*CHANNEL_SHM_SIZE) 15 | 16 | // Macros for accessing members inside a shm region 17 | #define SHM_PENDING_BIT(pos) (*((volatile unsigned int*)(pos))) 18 | #define SHM_PENDING_BIT_PTR(pos) ((unsigned int*)(pos)) 19 | #define SHM_SLEEPING_BIT(pos) (*((volatile unsigned int*)(pos+4))) 20 | #define SHM_SLEEPING_BIT_PTR(pos) (((unsigned int*)(pos+4))) 21 | #define SHM_DONE_BIT(pos) (*((volatile unsigned int*)(pos+8))) 22 | #define SHM_SKIP_WHEN_REOPEN_BIT(pos) (*((volatile unsigned int*)(pos+12))) 23 | #define SHM_DO_NOT_EXPAND_BIT(pos) (*((volatile unsigned int*)(pos+16))) 24 | #define SHM_CLICK_R(pos) (*((volatile unsigned short*)(pos+20))) 25 | #define SHM_CLICK_C(pos) (*((volatile unsigned short*)(pos+22))) 26 | #define SHM_OPENED_GRID_COUNT(pos) (*((volatile int*)(pos+24))) 27 | #define SHM_OPENED_GRID_ARR(pos) ((unsigned short (*)[16384][3])(pos+28)) 28 | 29 | // Open the shared memory (shm), and return a pointer pointing to its head 30 | char* open_shm(const char* shm_name); 31 | 32 | // Initialize a shm region 33 | // This is supposed to be called by the game server 34 | void init_shm_region(char* pos); 35 | 36 | // Generate a random shm name 37 | // Looks like `/minesweeper_shm_12556206635472641212` 38 | void generate_random_shm_name(char* result); 39 | 40 | #endif // __MINESWEEPER_SHM_H__ -------------------------------------------------------------------------------- /answer/naive_mt.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | naive_mt.cpp - A naive solution, with multithread optimization 3 | This solution just clicks all grids one by one, using multiple threads. 4 | 5 | It summons `THREAD_COUNT` threads. The i-th thread is responsible for 6 | clicking rows within the range [i*N/THREAD_COUNT, (i+1)*N/THREAD_COUNT) 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "minesweeper_helpers.h" 14 | 15 | constexpr int THREAD_COUNT = 8; 16 | 17 | void Pthread_create_wrapper(pthread_t *tidp, pthread_attr_t *attrp, 18 | void * (*routine)(void *), void *argp) 19 | { 20 | int rc; 21 | 22 | if ((rc = pthread_create(tidp, attrp, routine, argp)) != 0) 23 | fprintf(stderr, "Pthread_create error"); 24 | } 25 | 26 | void Pthread_join_wrapper(pthread_t tid, void **thread_return) { 27 | int rc; 28 | 29 | if ((rc = pthread_join(tid, thread_return)) != 0) 30 | fprintf(stderr, "Pthread_join error"); 31 | } 32 | 33 | 34 | 35 | long N, K; 36 | int constant_A; 37 | 38 | void* thread_routine(void* arg) { 39 | long thread_id = (long)arg; 40 | Channel channel = create_channel(); 41 | long row_start = thread_id*N/THREAD_COUNT; 42 | long row_end = (thread_id+1)*N/THREAD_COUNT; 43 | for (long r = row_start; r < row_end; ++r) { 44 | // printf("R = %ld\n", r); 45 | for (long c = 0; c < N; ++c) { 46 | channel.click(r, c, true); 47 | } 48 | } 49 | return 0; 50 | } 51 | 52 | int main() { 53 | minesweeper_init(N, K, constant_A); 54 | 55 | // Summon those worker threads 56 | pthread_t tids[THREAD_COUNT]; 57 | for (int i = 0; i < THREAD_COUNT; ++i) { 58 | Pthread_create_wrapper(tids+i, NULL, thread_routine, (void*)(long)i); 59 | } 60 | // Wait for worker threads to finish 61 | for (int i = 0; i < THREAD_COUNT; ++i) { 62 | Pthread_join_wrapper(tids[i], NULL); 63 | } 64 | 65 | return 0; 66 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .SECONDEXPANSION: 2 | 3 | CC = g++ 4 | CXXFLAGS ?= -g -Ofast -std=c++17 -Wall -march=native -Wl,--as-needed -pthread -lpthread -lrt # `-lrt` for `shm_open()` 5 | 6 | ANSWERS = template naive naive_mt naive_optim just_open_many_channels interact simple_expand_single_thread expand_with_queue expand_with_queue_mt 7 | LIBS = csapp wrappers minesweeper_helpers log common shm futex queue 8 | EXES = judger game_server map_generator map_visualizer blank_counter 9 | 10 | # files to be put into the `handout` directory 11 | HANDOUT_FILE_LIST = game_server.cpp judger.cpp map_generator.cpp map_visualizer.cpp \ 12 | answer/naive.cpp answer/naive_optim.cpp answer/interact.cpp generate_example_maps.py 13 | 14 | LIB_OBJS = $(foreach x, $(LIBS), $(addsuffix .o, $(x))) 15 | 16 | .PHONY: all handout clean 17 | 18 | all: $(EXES) $(ANSWERS) 19 | 20 | %.o: %.cpp # for executables 21 | $(CC) -c $(CXXFLAGS) $< -o $@ 22 | 23 | %.o: answer/%.cpp # for answer/?.cpp 24 | $(CC) -c $(CXXFLAGS) $< -o $@ 25 | 26 | %.o: lib/%.cpp lib/%.h # for lib/?.cpp 27 | $(CC) -c $(CXXFLAGS) $< -o $@ 28 | 29 | $(EXES): $$@.o $(LIB_OBJS) 30 | $(CC) $@.o $(LIB_OBJS) $(CXXFLAGS) -o $@ 31 | 32 | $(ANSWERS): $$@.o $(LIB_OBJS) 33 | $(CC) $@.o $(LIB_OBJS) $(CXXFLAGS) -o $@ 34 | 35 | handout: all 36 | rm -rf minesweeper_handout 37 | mkdir minesweeper_handout 38 | # copy executables and answers 39 | $(foreach file, $(HANDOUT_FILE_LIST), cp $(file) minesweeper_handout/.;) 40 | # copy libraries 41 | cp -r lib minesweeper_handout/lib 42 | cp lib/minesweeper_helpers.h minesweeper_handout/. 43 | # copy makefile 44 | cp Makefile_handout.mk minesweeper_handout/Makefile 45 | # copy answer template 46 | cp answer/template.cpp minesweeper_handout/answer.cpp 47 | # create the zipball 48 | rm -rf minesweeper_handout.zip 49 | zip -r -q minesweeper_handout.zip minesweeper_handout/ 50 | 51 | clean: 52 | rm -rf *.o $(EXES) $(ANSWERS) 53 | -------------------------------------------------------------------------------- /lib/wrappers.cpp: -------------------------------------------------------------------------------- 1 | #include "wrappers.h" 2 | 3 | void Pipe(int fds[2]) { 4 | if (pipe(fds) < 0) { 5 | unix_error("pipe error"); 6 | } 7 | } 8 | 9 | void Pipe(int &read_fd, int &write_fd) { 10 | int fds[2]; 11 | if (pipe(fds) < 0) { 12 | unix_error("pipe error"); 13 | } 14 | read_fd = fds[0]; 15 | write_fd = fds[1]; 16 | } 17 | 18 | void Usleep(useconds_t usec) { 19 | if (usleep(usec) < 0) { 20 | unix_error("usleep error"); 21 | } 22 | } 23 | 24 | void Setenv(const char* name, const char* value, int replace) { 25 | if (setenv(name, value, replace) < 0) { 26 | unix_error("setenv error"); 27 | } 28 | } 29 | 30 | char* Getenv(const char* name) { 31 | return getenv(name); 32 | } 33 | 34 | char* Getenv_must_exist(const char* name) { 35 | char* result; 36 | if ((result=getenv(name)) == NULL) { 37 | app_error("Environment variable %s does not exists.", name); 38 | } 39 | return result; 40 | } 41 | 42 | void Prctl(int option, unsigned long arg2) { 43 | if (prctl(option, arg2) < 0) { 44 | unix_error("prctl error"); 45 | } 46 | } 47 | 48 | int Shm_open(const char *name, int oflag, mode_t mode) { 49 | int rc; 50 | if ((rc = shm_open(name, oflag, mode)) == -1) { 51 | unix_error("shm_open error"); 52 | } 53 | return rc; 54 | } 55 | 56 | void Ftruncate(int fd, off_t length) { 57 | if (ftruncate(fd, length) == -1) { 58 | unix_error("ftruncate error"); 59 | } 60 | } 61 | 62 | void Fseek(FILE *stream, long offset, int whence) { 63 | if (fseek(stream, offset, whence) == -1) { 64 | unix_error("fseek error"); 65 | } 66 | } 67 | 68 | void Shm_unlink(const char* name) { 69 | int rc; 70 | if ((rc = shm_unlink(name)) < 0) { 71 | posix_error(rc, "shm_unlink error"); 72 | } 73 | } 74 | 75 | void Pthread_setcancelstate(int state, int* oldstate) { 76 | int rc; 77 | if ((rc = pthread_setcancelstate(state, oldstate)) < 0) { 78 | posix_error(rc, "pthread_setcancelstate error"); 79 | } 80 | } 81 | 82 | void Pthread_setcanceltype(int type, int* oldtype) { 83 | int rc; 84 | if ((rc = pthread_setcanceltype(type, oldtype)) < 0) { 85 | posix_error(rc, "pthread_setcanceltype error"); 86 | } 87 | } -------------------------------------------------------------------------------- /lib/wrappers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * wrappers.h - various wrapper functions for unix functions 3 | * A function wrapper checks the return value of the original function, and 4 | * when an error is encountered, it prints out an error message and exits. 5 | */ 6 | 7 | #ifndef __MINESWEEPER_WRAPPERS_H__ 8 | #define __MINESWEEPER_WRAPPERS_H__ 9 | 10 | #include 11 | #include 12 | #include "csapp.h" // include all wrapper functions from csapp.h 13 | 14 | /* Create a one-way communication channel (pipe). 15 | If successful, two file descriptors are stored in PIPEDES; 16 | bytes written on fds[1] can be read from fds[0]. */ 17 | void Pipe(int fds[2]); 18 | 19 | /* Create a one-way communication channel (pipe). 20 | If successful, two file descriptors are stored. 21 | Bytes can be written to write_fd and read from read_fd. */ 22 | void Pipe(int &read_fd, int &write_fd); 23 | 24 | /* Sleep USECONDS microseconds, or until a signal arrives that is not blocked 25 | or ignored. */ 26 | void Usleep(useconds_t usec); 27 | 28 | /* Set NAME to VALUE in the environment. 29 | If REPLACE is nonzero, overwrite an existing value. */ 30 | void Setenv(const char* name, const char* value, int replace); 31 | 32 | /* Return the value of envariable NAME, or NULL if it doesn't exist. */ 33 | char* Getenv(const char* name); 34 | 35 | /* Return the value of envariable NAME. If it does not exist, 36 | report an error and exit. */ 37 | char* Getenv_must_exist(const char* name); 38 | 39 | /* Execute PATH with all arguments after PATH until 40 | a NULL pointer */ 41 | #define Execl(...) {execl(__VA_ARGS__); unix_error("execl error");} 42 | 43 | void Prctl(int option, unsigned long arg2); 44 | 45 | /* Open shared memory segment. */ 46 | int Shm_open(const char *name, int oflag, mode_t mode); 47 | 48 | /* Truncate the file FD is open on to LENGTH bytes. */ 49 | void Ftruncate(int fd, off_t length); 50 | 51 | /* Seek to a certain position on STREAM. */ 52 | void Fseek(FILE *stream, long offset, int whence); 53 | 54 | /* Remove shared memory segment. */ 55 | void Shm_unlink(const char* name); 56 | 57 | /* Set cancelability state of current thread to STATE, returning old 58 | state in *OLDSTATE if OLDSTATE is not NULL. */ 59 | void Pthread_setcancelstate(int state, int* oldstate); 60 | 61 | /* Set cancellation state of current thread to TYPE, returning the old 62 | type in *OLDTYPE if OLDTYPE is not NULL. */ 63 | void Pthread_setcanceltype(int type, int* oldtype); 64 | 65 | #endif // __MINESWEEPER_WRAPPERS_H__ -------------------------------------------------------------------------------- /map_visualizer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | map_visualizer - Visualize a map for the game minesweeper 3 | It reads the map from a file, and prints the map (in a human-readable format) 4 | onto the screen. 5 | 6 | Usage: ./map_visualizer 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "lib/wrappers.h" 14 | 15 | #define DISPLAY_THRESHOLD_N 2048 16 | 17 | void usage(char* prog_name) { 18 | printf("Usage: %s \n", prog_name); 19 | exit(0); 20 | } 21 | 22 | long N, K, logN; 23 | char* is_mine; 24 | 25 | char test_is_mine(long r, long c) { 26 | if (r < 0 || c < 0 || r >= N || c >= N) return 0; 27 | long index = (r<>offset&0x1; 30 | } 31 | 32 | int main(int argc, char* argv[]) { 33 | if (argc != 2) { 34 | usage(argv[0]); 35 | } 36 | 37 | FILE* map_file = fopen(argv[1], "r"); 38 | if (!map_file) { 39 | unix_error("Failed to open the map file"); 40 | } 41 | printf("File: %s\n", argv[1]); 42 | 43 | if (fscanf(map_file, "%ld %ld\n", &N, &K) != 2) { 44 | app_error("Failed to read N and K. Maybe the map file is broken?"); 45 | } 46 | 47 | printf("Size(N): %ld\nNumber of mines(K): %ld\n", N, K); 48 | logN = (long)(log2((double)N)+0.01); 49 | 50 | if (N > DISPLAY_THRESHOLD_N) { 51 | app_error("This map is too large.\n\ 52 | Displaying it may stuck or crash your terminal (especially when you are using a code server).\n\ 53 | If you really want to display it, please change DISPLAY_THRESHOLD_N in \ 54 | map_visualizer to a larger number, and recompile it.\n\ 55 | P.S. the default value of DISPLAY_THRESHOLD_N is 2048."); 56 | } 57 | 58 | Fseek(map_file, 0, SEEK_SET); 59 | char c; 60 | while ((c = fgetc(map_file)) != '\n'); 61 | 62 | is_mine = (char*)Malloc(N*N/8); 63 | size_t byte_read = fread(is_mine, 1, N*N/8, map_file); 64 | if (byte_read != N*N/8) { 65 | app_error("Failed to read the map. Maybe the map file is broken?"); 66 | } 67 | 68 | long mine_cnt = 0; 69 | for (long r = 0; r < N; ++r) { 70 | for (long c = 0; c < N; ++c) { 71 | if (test_is_mine(r, c)) { 72 | putchar('*'); 73 | mine_cnt += 1; 74 | } else { 75 | char cnt = test_is_mine(r-1, c-1) + test_is_mine(r-1, c) + test_is_mine(r-1, c+1) 76 | + test_is_mine(r, c-1) + test_is_mine(r, c) + test_is_mine(r, c+1) 77 | + test_is_mine(r+1, c-1) + test_is_mine(r+1, c) + test_is_mine(r+1, c+1); 78 | putchar(cnt+'0'); 79 | } 80 | } 81 | putchar('\n'); 82 | } 83 | assert(mine_cnt == K); 84 | 85 | return 0; 86 | } -------------------------------------------------------------------------------- /answer/interact.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | interact.cpp - An interactive program which allows the player plays the minesweeper 3 | game in the console with the game server. 4 | */ 5 | #include 6 | #include 7 | #include 8 | 9 | #include "minesweeper_helpers.h" 10 | 11 | constexpr int MAXN = 256; 12 | 13 | long N, K; 14 | int constant_A; 15 | 16 | // -1: unknown. -2: mine. >=0: empty grid 17 | char map[MAXN][MAXN]; 18 | 19 | // Print the map on the terminal 20 | void show_map() { 21 | // Print the header 22 | printf(" "); 23 | for (int i = 0; i < N; ++i) 24 | printf("%c", i%10+'0'); 25 | putchar('\n'); 26 | printf(" "); 27 | for (int i = 0; i < N; ++i) 28 | putchar('_'); 29 | putchar('\n'); 30 | // Print the map 31 | for (int r = 0; r < N; ++r) { 32 | // Print the left header 33 | printf("%7d|", r); 34 | // Print the map 35 | for (int c = 0; c < N; ++c) { 36 | if (map[r][c] == -1) putchar(' '); // unknown 37 | else if (map[r][c] == -2) putchar('X'); // mine 38 | else putchar(map[r][c]+'0'); 39 | } 40 | // Print the right header 41 | printf("|%d", r); 42 | putchar('\n'); 43 | } 44 | // Print the footer 45 | putchar('\t'); 46 | for (int i = 0; i < N; ++i) 47 | putchar('-'); 48 | putchar('\n'); 49 | putchar('\t'); 50 | for (int i = 0; i < N; ++i) 51 | printf("%c", i%10+'0'); 52 | putchar('\n'); 53 | } 54 | 55 | int main() { 56 | memset(map, 0xff, sizeof(map)); // set to -1 57 | 58 | minesweeper_init(N, K, constant_A); 59 | 60 | if (N > MAXN) { 61 | printf("We strongly do not advise you to use this solution on maps with side length > %d\n", MAXN); 62 | printf("Because the map would be too big to display.\n"); 63 | printf("P.S. The side length of the current map is %ld\n", N); 64 | printf("If you really want to play on a larger map, please change `MAXN` in `interact.cpp` and recompile it.\n"); 65 | exit(1); 66 | } 67 | 68 | // 创建用于和 game server 通信的 Channel 69 | Channel channel = create_channel(); 70 | 71 | while (true) { 72 | show_map(); 73 | 74 | printf("Please enter the coordinate you want to click on, type `Q` to quit.\n"); 75 | printf("> "); fflush(stdout); 76 | 77 | // Read the user's input 78 | char buf[64]; 79 | std::cin.getline(buf, 64); 80 | if (buf[0] == 'Q' || buf[0] == 'q') { 81 | break; 82 | } 83 | int click_r, click_c; 84 | int num_read = sscanf(buf, "%d %d", &click_r, &click_c); 85 | if (num_read != 2) { 86 | printf("Bad input\n"); 87 | continue; 88 | } 89 | 90 | // Click it 91 | ClickResult result = channel.click(click_r, click_c, false); 92 | 93 | // Parse the click result 94 | if (result.is_mine) { 95 | printf("BOOM!\n"); 96 | map[click_r][click_c] = -2; 97 | } else { 98 | printf("%d grids opened\n", result.open_grid_count); 99 | // 遍历所有点开的格子,并把它们标注在 map[][] 数组中 100 | auto arr = *result.open_grid_pos; 101 | for (int i = 0; i < result.open_grid_count; ++i) { 102 | map[arr[i][0]][arr[i][1]] = arr[i][2]; 103 | } 104 | } 105 | } 106 | 107 | return 0; 108 | } -------------------------------------------------------------------------------- /blank_counter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | blank_counter - A tool for counting the maximum connected component of zeros 3 | in a map. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "lib/wrappers.h" 12 | using std::max, std::queue; 13 | 14 | void usage(char* prog_name) { 15 | printf("Usage: %s \n", prog_name); 16 | exit(0); 17 | } 18 | 19 | long N, K, logN; 20 | char* is_mine; 21 | 22 | char test_is_mine(long r, long c) { 23 | if (r < 0 || c < 0 || r >= N || c >= N) return 1; 24 | long index = (r<>offset&0x1; 27 | } 28 | 29 | char* vis; 30 | char test_is_vis(long r, long c) { 31 | if (r < 0 || c < 0 || r >= N || c >= N) return 0; 32 | long index = (r<>offset&0x1; 35 | } 36 | 37 | void mark_vis(long r, long c) { 38 | long index = (r< q; 53 | long bfs(long st_r, long st_c) { 54 | long count = 0; 55 | q.push(st_r<>logN, c = q_front&(N-1); 59 | q.pop(); 60 | count += 1; 61 | for (int k = 0; k < 8; ++k) { 62 | int new_r = r + delta_xy[k][0], new_c = c + delta_xy[k][1]; 63 | if (!test_is_mine(new_r, new_c) && !test_is_vis(new_r, new_c)) { 64 | if (count_adj_mine(new_r, new_c) == 0) { 65 | mark_vis(new_r, new_c); 66 | q.push(new_r< 6 | #include 7 | using std::max, std::min; 8 | 9 | #include "csapp.h" 10 | #include "minesweeper_helpers.h" 11 | constexpr int MAXN = 65536; 12 | constexpr int delta_xy[8][2] = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}}; 13 | 14 | int N, K; 15 | int constant_A; 16 | 17 | Channel channel; 18 | 19 | long cnt_empty_opened = 0; 20 | 21 | char* map[MAXN]; 22 | constexpr char MAP_UNKNOWN = -1; 23 | constexpr char MAP_MINE = -2; 24 | 25 | inline bool map_eq(int r, int c, char x) { 26 | if (r < 0 || c < 0 || r >= N || c >= N) return false; 27 | return map[r][c] == x; 28 | } 29 | inline bool map_is_empty(int r, int c) { 30 | if (r < 0 || c < 0 || r >= N || c >= N) return false; 31 | return map[r][c] >= 0; 32 | } 33 | 34 | int count_adj_unknown(int r, int c) { 35 | return (int)map_eq(r-1, c-1, MAP_UNKNOWN) + map_eq(r-1, c, MAP_UNKNOWN) + map_eq(r-1, c+1, MAP_UNKNOWN) 36 | + map_eq(r, c-1, MAP_UNKNOWN) + map_eq(r, c+1, MAP_UNKNOWN) 37 | + map_eq(r+1, c-1, MAP_UNKNOWN) + map_eq(r+1, c, MAP_UNKNOWN) + map_eq(r+1, c+1, MAP_UNKNOWN); 38 | } 39 | int count_adj_mine(int r, int c) { 40 | return (int)map_eq(r-1, c-1, MAP_MINE) + map_eq(r-1, c, MAP_MINE) + map_eq(r-1, c+1, MAP_MINE) 41 | + map_eq(r, c-1, MAP_MINE) + map_eq(r, c+1, MAP_MINE) 42 | + map_eq(r+1, c-1, MAP_MINE) + map_eq(r+1, c, MAP_MINE) + map_eq(r+1, c+1, MAP_MINE); 43 | } 44 | int count_adj_empty(int r, int c) { 45 | return (int)map_is_empty(r-1, c-1) + map_is_empty(r-1, c) + map_is_empty(r-1, c+1) 46 | + map_is_empty(r, c-1) + map_is_empty(r, c+1) 47 | + map_is_empty(r+1, c-1) + map_is_empty(r+1, c) + map_is_empty(r+1, c+1); 48 | } 49 | 50 | void click_and_put_result(int r, int c) { 51 | ClickResult result = channel.click(r, c, true); 52 | if (result.is_skipped) return; 53 | if (result.is_mine) { 54 | map[r][c] = MAP_MINE; 55 | } else { 56 | auto open_grid_pos = *result.open_grid_pos; 57 | for (int i = 0; i < result.open_grid_count; ++i) { 58 | if (map[open_grid_pos[i][0]][open_grid_pos[i][1]] == MAP_UNKNOWN) 59 | cnt_empty_opened += 1; 60 | map[open_grid_pos[i][0]][open_grid_pos[i][1]] = open_grid_pos[i][2]; 61 | } 62 | } 63 | } 64 | 65 | bool try_solve(int r, int c) { 66 | if (map[r][c] <= 0) return false; // Not an empty grid, or the number inside it is 0 67 | int adj_unknown = count_adj_unknown(r, c); 68 | if (adj_unknown == 0) return false; 69 | int adj_mine = count_adj_mine(r, c); 70 | if (adj_mine == map[r][c]) { 71 | // all adjacent&unknown grids are empty grids 72 | for (int k = 0; k < 8; ++k) { 73 | int new_r = r+delta_xy[k][0], new_c = c+delta_xy[k][1]; 74 | if (new_r < 0 || new_c < 0 || new_r >= N || new_c >= N) continue; 75 | if (map[new_r][new_c] == MAP_UNKNOWN) { 76 | click_and_put_result(new_r, new_c); 77 | } 78 | } 79 | return true; 80 | } 81 | // int adj_empty = count_adj_empty(r, c); 82 | if (adj_unknown+adj_mine == map[r][c]) { 83 | // all adjacent&unknown grids are mines 84 | for (int k = 0; k < 8; ++k) { 85 | int new_r = r+delta_xy[k][0], new_c = c+delta_xy[k][1]; 86 | if (new_r < 0 || new_c < 0 || new_r >= N || new_c >= N) continue; 87 | map[new_r][new_c] = MAP_MINE; 88 | } 89 | return true; 90 | } 91 | return false; 92 | } 93 | 94 | bool try_solve_region(int r1, int c1, int r2, int c2) { 95 | bool flag = false; 96 | for (int r = r1; r < r2; ++r) 97 | for (int c = c1; c < c2; ++c) 98 | flag |= try_solve(r, c); 99 | return flag; 100 | } 101 | 102 | int main() { 103 | minesweeper_init(N, K, constant_A); 104 | for (int i = 0; i < N; ++i) { 105 | map[i] = (char*)Malloc(N); 106 | memset(map[i], MAP_UNKNOWN, N); 107 | } 108 | channel = create_channel(); 109 | 110 | int last_r = 0; 111 | click_and_put_result(0, 0); 112 | for (int i = 0; i < 128 && cnt_empty_opened < (long)N*N-K; ++i) { 113 | // printf("%ld\n", cnt_empty_opened); 114 | for (int r = max(0, last_r-1); r < N; ++r) { 115 | for (int c = 0; c < N; ++c) { 116 | if (try_solve(r, c)) { 117 | while (try_solve_region(max(r-6, 0), max(c-6, 0), min(r+6, N-1), min(c+6, N-1))); 118 | // goto mid; 119 | } 120 | } 121 | } 122 | // mid:; 123 | for (int r = last_r; r < N; ++r) { 124 | for (int c = 0; c < N; ++c) { 125 | if (map[r][c] == MAP_UNKNOWN) { 126 | click_and_put_result(r, c); 127 | last_r = r; 128 | goto next_loop; 129 | } 130 | } 131 | } 132 | next_loop:; 133 | } 134 | 135 | for (int r = 0; r < N; ++r) { 136 | for (int c = 0; c < N && cnt_empty_opened < (long)N*N-K; ++c) { 137 | if (map[r][c] == MAP_UNKNOWN) { 138 | click_and_put_result(r, c); 139 | while (try_solve_region(max(r-7, 0), max(c-7, 0), min(r+7, N-1), min(c+7, N-1))); 140 | } 141 | } 142 | } 143 | 144 | 145 | return 0; 146 | } -------------------------------------------------------------------------------- /map_generator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | map_generator - generate a map for the game minesweeper 3 | 4 | The map looks like: 5 | ```plain 6 | 4 7 7 | 8 | ``` 9 | Where 4 is the side length of the map, and 7 is the number of mines. 10 | 11 | Usage: ./map_generator 12 | It prints the map to the stdout. 13 | 14 | Principle: 15 | - Stage 1: It first create NUM_THREAD threads, and each thread puts mines 16 | in the map randomly (without the guarantee that one thread put mines in 17 | all-unique locations. The i-th thread is responsible for putting mines 18 | in rows within the range [i*(N/NUM_THREAD), (i+1)*(N/NUM_THREAD). 19 | - Stage 2: Then it exams how many mines are put, and put the mines left 20 | with one thread. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "lib/wrappers.h" 28 | 29 | void usage(char* prog_name) { 30 | printf("Usage: %s [seed]\n", prog_name); 31 | printf("\t`N` is the side length of the map\n"); 32 | printf("\t`K` is the number of mines\n"); 33 | printf("\t`seed` is the random seed for the random number generator. "); 34 | printf("If it is not present, then the current time is used"); 35 | exit(0); 36 | } 37 | 38 | constexpr int NUM_THREAD = 16; 39 | 40 | std::mt19937 rng; 41 | 42 | long N, K, seed; 43 | int logN; 44 | 45 | char* is_mine; // A big array. It ith-bit indicate whether (i/N, i%N) is a mine or not 46 | 47 | inline void put_mine(uint r, uint c) { 48 | long index = r<>offset&0x1; 63 | } 64 | 65 | // The thread routine. Put (at most) `num_mine` mines in the map randomly 66 | void* thread_routine(void* argp) { 67 | long arg = (long)argp; 68 | long seed = arg>>32&0xffffffff; 69 | uint thread_id = arg&0xffffffff; 70 | std::mt19937 local_rng(seed); // std::mt19937 is not thread safe. So we must create one for each thread 71 | 72 | uint num_mine = K/NUM_THREAD*1.1; 73 | uint row_mask = N/NUM_THREAD-1; 74 | uint col_mask = N-1; 75 | for (uint i = 0; i < num_mine; ++i) { 76 | uint t = local_rng(); // Since calling `rng()` is slow, we generate it once and extract r and c from it 77 | uint r = (t&row_mask) + thread_id*(N/NUM_THREAD); // Since N is power of 2, doing this won't result in ununiform distribution 78 | uint c = (t>>16)&col_mask; 79 | put_mine(r, c); 80 | } 81 | 82 | return NULL; 83 | } 84 | 85 | int main(int argc, char* argv[]) { 86 | // Initialization 87 | if (argc != 3 && argc != 4) { 88 | usage(argv[0]); 89 | } 90 | 91 | N = atol(argv[1]); 92 | K = atol(argv[2]); 93 | if (N <= 0 || K <= 0 || K > N*N) { 94 | usage(argv[0]); 95 | } 96 | logN = (int)(log2((double)N)+0.01); 97 | if ((1< 65536) { 107 | app_error("N must be less or equal to 65536"); 108 | } 109 | 110 | if (argc == 4) { 111 | seed = atol(argv[3]); 112 | } else { 113 | seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); 114 | } 115 | rng = std::mt19937(seed); 116 | 117 | is_mine = (char*)Calloc(N*N/8, 1); 118 | 119 | // Stage 1: Create some threads and begin to put mines in the map 120 | pthread_t tids[NUM_THREAD]; 121 | for (int i = 0; i < NUM_THREAD; ++i) { 122 | long seed = rng()+1; 123 | long arg = seed<<32 | i; 124 | Pthread_create(tids+i, NULL, thread_routine, (void*)arg); 125 | } 126 | for (int i = 0; i < NUM_THREAD; ++i) { 127 | Pthread_join(tids[i], NULL); 128 | } 129 | 130 | 131 | // Stage 2: Count the number of put mines, and put the lacked mines 132 | long mine_put = 0; 133 | for (long i = 0; i < N*N/64; ++i) { 134 | mine_put += __builtin_popcountll(((long*)is_mine)[i]); 135 | } 136 | // printf("%ld\n", mine_put); 137 | if (mine_put < K) { 138 | uint mask = (1<>16)&mask; 145 | } while (test_is_mine(r, c)); 146 | put_mine(r, c); 147 | } 148 | } else { 149 | uint mask = (1<>16)&mask; 156 | } while (!test_is_mine(r, c)); 157 | unput_mine(r, c); 158 | } 159 | } 160 | 161 | // mine_put = 0; 162 | // for (long i = 0; i < N*N/64; ++i) { 163 | // mine_put += __builtin_popcountll(((long*)is_mine)[i]); 164 | // } 165 | // printf("%ld\n", mine_put); 166 | 167 | 168 | // Stage 3: Print the map out 169 | // We use fwrite() to speed up writing 170 | printf("%ld %ld\n", N, K); 171 | size_t byte_write = fwrite((char*)is_mine, 1, N*N/8, stdout); 172 | if (byte_write != N*N/8) { 173 | fprintf(stderr, "Error! Failed to write the map into the file.\n"); 174 | fprintf(stderr, "I want to write %ld bytes, but only %ld bytes are written.\n", N*N/8, byte_write); 175 | } 176 | fflush(stdout); 177 | return 0; 178 | } -------------------------------------------------------------------------------- /lib/minesweeper_helpers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "wrappers.h" 3 | #include "common.h" 4 | #include "shm.h" 5 | #include "log.h" 6 | #include "futex.h" 7 | #include "minesweeper_helpers.h" 8 | 9 | static char* shm_name; 10 | static char* shm_start; 11 | static int fd_from_gs, fd_to_gs; 12 | static long _N, _K; 13 | 14 | void minesweeper_init(long &N, long &K, int &constant_A) { 15 | static bool is_minesweeper_init_called_before = false; 16 | if (is_minesweeper_init_called_before) { 17 | log("Error! Please call `minesweeper_init` only once.\n"); 18 | exit(1); 19 | } 20 | prog_name = "Player's Program"; 21 | if (!Getenv("MINESWEEPER_LAUNCHED_BY_JUDGER")) { 22 | log("Error! This program (player's program) is not designed for starting\ 23 | manually. Please invoke the judger.\n"); 24 | exit(1); 25 | } 26 | // Set the process to exit when its parent dies 27 | exit_when_parent_dies(); 28 | // Open the shared memory (shm) region 29 | shm_name = Getenv_must_exist("MINESWEEPER_SHM_NAME"); 30 | shm_start = open_shm(shm_name); 31 | // Get fds 32 | fd_from_gs = atoi(Getenv_must_exist("MINESWEEPER_FD_PL_FROM_GS")); 33 | fd_to_gs = atoi(Getenv_must_exist("MINESWEEPER_FD_PL_TO_GS")); 34 | // Read N and K from `fd_from_gs` 35 | char buf[64]; 36 | Read(fd_from_gs, buf, 64); 37 | int rc = sscanf(buf, "%ld %ld", &N, &K); 38 | if (rc != 2) { 39 | log("Error! Did not read enough numbers (N and K) from `fd_from_gs` in `minesweeper_init()`.\n"); 40 | log("The game server sent \"%s\"\n", buf); 41 | exit(1); 42 | } 43 | _N = N; _K = K; 44 | // Get constant_A 45 | constant_A = atoi(Getenv_must_exist("MINESWEEPER_CONSTANT_A")); 46 | } 47 | 48 | void minesweeper_init(int &N, int &K, int &constant_A) { 49 | long _N, _K; 50 | minesweeper_init(_N, _K, constant_A); 51 | N = _N; K = _K; 52 | } 53 | 54 | pthread_mutex_t create_channel_mutex = PTHREAD_MUTEX_INITIALIZER; 55 | Channel create_channel(void) { 56 | Pthread_mutex_lock(&create_channel_mutex); 57 | Channel result; 58 | // Send 'C' to the game server, through `fd_to_gs` 59 | char c = 'C'; 60 | Write(fd_to_gs, &c, 1); 61 | // Get the channel ID 62 | char buf[16]; 63 | Read(fd_from_gs, buf, 16); 64 | sscanf(buf, "%d", &result.id); 65 | // Calculate `shm_pos` 66 | result.shm_pos = shm_start + result.id*CHANNEL_SHM_SIZE; 67 | Pthread_mutex_unlock(&create_channel_mutex); 68 | return result; 69 | } 70 | 71 | ClickResult Channel::click(long r, long c, bool skip_when_reopen) { 72 | if (r < 0 || c < 0 || r >= _N || c >= _N) { 73 | log("Error! The player's program called `click(r, c)` with invalid arguments:\n"); 74 | log("R = %ld, C = %ld\n", r, c); 75 | exit(1); 76 | } 77 | char* shm_pos = this->shm_pos; 78 | ClickResult result; 79 | result.is_skipped = false; 80 | // Fill in `click_r` and `click_c` 81 | SHM_CLICK_R(shm_pos) = (unsigned short)r; 82 | SHM_CLICK_C(shm_pos) = (unsigned short)c; 83 | SHM_SKIP_WHEN_REOPEN_BIT(shm_pos) = skip_when_reopen; 84 | SHM_DO_NOT_EXPAND_BIT(shm_pos) = 0; 85 | 86 | // Wake up the corresponding thread in the game server 87 | SHM_PENDING_BIT(shm_pos) = 1; 88 | if (SHM_SLEEPING_BIT(shm_pos)) { 89 | futex_wake(SHM_PENDING_BIT_PTR(shm_pos)); 90 | } 91 | // Wait for the game server to complete the request (by spinning) 92 | while (!SHM_DONE_BIT(shm_pos)) { 93 | if (SHM_SLEEPING_BIT(shm_pos)) { 94 | futex_wake(SHM_PENDING_BIT_PTR(shm_pos)); 95 | } 96 | } 97 | // Copy the result 98 | int open_grid_count = SHM_OPENED_GRID_COUNT(shm_pos); 99 | if (open_grid_count == -1) { 100 | // The grid contains a mine, BOOM! 101 | result.is_mine = true; 102 | } else if (open_grid_count == -2 || open_grid_count == -3) { 103 | // skipped because of re-open 104 | result.is_mine = open_grid_count == -3; 105 | result.is_skipped = true; 106 | } else { 107 | result.is_mine = false; 108 | result.open_grid_count = open_grid_count; 109 | result.open_grid_pos = SHM_OPENED_GRID_ARR(shm_pos); 110 | } 111 | SHM_DONE_BIT(shm_pos) = 0; 112 | return result; 113 | } 114 | 115 | ClickResult Channel::click_do_not_expand(long r, long c) { 116 | if (r < 0 || c < 0 || r >= _N || c >= _N) { 117 | log("Error! The player's program called `click(r, c)` with invalid arguments:\n"); 118 | log("R = %ld, C = %ld\n", r, c); 119 | exit(1); 120 | } 121 | char* shm_pos = this->shm_pos; 122 | ClickResult result; 123 | result.is_skipped = false; 124 | SHM_CLICK_R(shm_pos) = (unsigned short)r; 125 | SHM_CLICK_C(shm_pos) = (unsigned short)c; 126 | SHM_SKIP_WHEN_REOPEN_BIT(shm_pos) = 0; 127 | SHM_DO_NOT_EXPAND_BIT(shm_pos) = 1; 128 | 129 | // Wake up the corresponding thread in the game server 130 | SHM_PENDING_BIT(shm_pos) = 1; 131 | if (SHM_SLEEPING_BIT(shm_pos)) { 132 | futex_wake(SHM_PENDING_BIT_PTR(shm_pos)); 133 | } 134 | // Wait for the game server to complete the request (by spinning) 135 | while (!SHM_DONE_BIT(shm_pos)) { 136 | // We need to check `SHM_SLEEPING_BIT(shm_pos)` again and again, because 137 | // of cache coherence problem, that is, a modification on the main memory 138 | // by a process will not be reflexed on another process immediately. 139 | if (SHM_SLEEPING_BIT(shm_pos)) { 140 | futex_wake(SHM_PENDING_BIT_PTR(shm_pos)); 141 | } 142 | } 143 | // Copy the result 144 | int open_grid_count = SHM_OPENED_GRID_COUNT(shm_pos); 145 | if (open_grid_count == -1) { 146 | // The grid contains a mine, BOOM! 147 | result.is_mine = true; 148 | } else { 149 | result.is_mine = false; 150 | // assert (open_grid_count == 1); 151 | result.open_grid_count = 1; 152 | result.open_grid_pos = SHM_OPENED_GRID_ARR(shm_pos); 153 | } 154 | SHM_DONE_BIT(shm_pos) = 0; 155 | return result; 156 | } -------------------------------------------------------------------------------- /statement/zh-cn.md: -------------------------------------------------------------------------------- 1 | ## 题目背景 2 | 3 | 你很喜欢玩扫雷。 4 | 5 | 在游戏“扫雷”中,你面对的是一个由若干方格组成的矩阵,每个方格中可能有一颗地雷,也可能没有地雷。 6 | 7 | 你的基本操作名叫“点开”。每一次,你可以选择一个格子并“点开”它,如果它是地雷,那么你的分数会被扣掉一些,如果它不是地雷,那么你会获得一分,并且会有一个数字显现在方格上,这个数字代表着周围的的八个方格(上、下、左、右、左上、左下、右上、右下)中一共有多少颗地雷(数字为 0 到 8)。如果点开的方格中的数字为 0(这代表这个方格周围的八个方格都不是地雷),那么它周围的方格也会被自动“点开”(这个过程可以递归,也即,如果新的方格中的数字也是 0,那么它周围的八个方格也会被自动“点开”。我们称其为“间接点开”,相对应的,被玩家直接点开的格子称为“直接点开”)。当玩家主动退出,或者时间耗尽后,游戏结束。 8 | 9 | **请注意,本题的扫雷与标准的扫雷有一些区别:** 10 | 11 | - 在标准的扫雷中,点开一个包含地雷的方格后,游戏会立刻失败 12 | - 当玩家程序点开所有非地雷方格后,游戏不会自动结束,需要玩家程序主动退出。 13 | - 每次“点开”一个包含数字 0 的格子时,你的程序会收到点开的格子所在的连通块中的所有数字为 0 的格子以及边缘那些数字不为 0 的格子,哪怕这些格子已经被点开过。这个在后文有具体的描述。 14 | 15 | 下面是 KDE 桌面系统中的“扫雷”游戏的例子(红色的小旗子代表这个方格中有地雷,空白的格子表示格子中的数字为 0): 16 | 17 | ![引自https://commons.wikimedia.org/wiki/File:Minesweeper_end_Kmines.png](https://hpcgame.pku.edu.cn/oss/images/Minesweeper_end_Kmines.png) 18 | 19 | 20 | 经过夜以继日的练习后,高级版扫雷的 $30 \times 16$ 的地图规模对你来说已经是小菜一碟。于是,你决定玩点刺激的。比如,$65536 \times 65535$ 的扫雷? 21 | 22 | 当然,如此大规模的扫雷肯定不能手工完成。于是,你打算把毕生所学融入到一个程序中,让这个程序来自动化地扫雷。 23 | 24 | ## 题目描述 25 | 26 | 你需要写一个(可能是并行的)程序,与我们提供的扫雷 game server 交互。 27 | 28 | 你可以使用下列库函数来与 game server 交互: 29 | 30 | - `void minesweeper_init(int &N, int &K, int &constant_A);` 在程序开始前,请调用此函数来初始化程序。这个函数会把地图的边长 $N$ 存放在变量 `N` 中,把地图中的雷的数量 $K$ 存放在变量 `K` 中,将评分参数 $A$(详见下文)存放在变量 `constant_A` 中。 31 | 32 | - `Channel create_channel(void);` 请使用此函数创建一个用于和 game server 通信的 `Channel` 对象。保证不同的 `Channel` 之间互不干扰,但若有两个进程 / 线程同时操作同一个 `Channel`,那么大概率会出错。所以建议为每个进程 / 线程单独开一个 `Channel`。 33 | 34 | 您可以使用此函数创建至多 1024 个 Channel,不过我建议您尽量保证活跃(反复调用 `click()`)的 Channel 不超过 8 个。毕竟对于每个您创建的 Channel,game server 中都有一个独立的线程负责处理该 Channel 中的请求。我们在评测的时候仅提供 16 个核心(注:没有超线程),故如果活跃的 Channel 超过 8 个,那么将发生频繁的 context switch,进而在很大程度上影响性能。 35 | 36 | - `ClickResult Channel::click(int r, int c, bool skip_when_reopen);` 这是 `Channel` 的成员函数,代表“点开 $(r, c)$ 位置所对应的格子”这一操作,$0 \le r, c < n$,返回点开的结果。 37 | 38 | `ClickResult` 类的定义以及成员变量详见 `minesweeper_helpers.h`。 39 | 40 | 每次“点开”一个包含数字 0 的格子时,你的程序会收到点开的格子所在的连通块中的所有数字为 0 的格子以及边缘那些数字不为 0 的格子,哪怕这些格子已经被点开过。举个例子:假设棋盘如下(X 代表地雷,左上角坐标为 $(0, 0)$,右上角坐标为 $(0, 2)$,右下角坐标为 $(2, 2)$): 41 | 42 | ``` 43 | 0 1 X 44 | 1 2 1 45 | X 1 0 46 | ``` 47 | 48 | 那么在调用 `click(0, 0, false)` 时,点开的格子包含 $(0, 0), (0, 1), (1, 0), (1, 1)$;接下来若调用 `click(2, 2, false)`,那么点开的格子包含 $(1, 1), (1, 2), (2, 1), (2, 2)$。请注意在两次调用中,$(1, 1)$ 这个格子都被点开了。对应到程序中,即为 $(1, 1)$ 这个点在两次 `click()` 返回的 `ClickResult` 的 `open_grid_pos` 数组中均出现了。 49 | 50 | 如果 `skip_when_open` 为 `true`,那么如果这个 $(r, c)$ 这个格子之前就被点开过(不论是被本 `Channel` 还是其他 `Channel` 点开,也不论是被直接点开还是被间接点开),那么 `click` 函数会立刻返回一个 `is_skipped=true` 的 `ClickResult`(注:提供本操作的主要目的是节约时间)。请注意,该选项保证如果返回 `is_skipped=true`,那么 $(r, c)$ 这个格子之前一定被点开过;但是,如果两个线程近似同时点开了同一个格子(或者二者互相间接点开了对方的格子),那么有一定概率两者都没有 skip。换句话说,这里存在一些 data race 的问题。 51 | 52 | 本函数的复杂度为:$\Theta({\small\text{点开的格子的数量}})$。如果 `skip_when_open=true` 且该格子之前就被点开,或者格子中含有雷,那么该函数的复杂度为 $\Theta(1)$。 53 | 54 | - `ClickResult Channel::click_do_not_expand(int r, int c);` 这是 `Channel` 的成员函数,代表“点开 $(r, c)$ 位置所对应的格子,并且不要间接点开”这一操作。 55 | 56 | 与上面的 `click()` 函数不同的是,这个函数不会做间接点开。换句话说,假设 $(r, c)$ 这个格子中的数字是 $0$,且周围格子中的数字都不是 $0$,那么 `click(r, c)` 会点开并返回 $(r, c)$ 以及其周围的格子,而 `click_do_not_expand(r, c)` 只会点开并返回 $(r, c)$ 这一个格子。 57 | 58 | 本函数复杂度恒为 $\Theta(1)$。 59 | 60 | (注:出题人写完标程之后发现,这个函数好像比上面那个 `click()` 更常用…) 61 | 62 | 这些函数均定义在了 `minesweeper_helpers.h` 中。你可以在程序开头加入 `#include "minesweeper_helpers.h"` 以使用这些函数。 63 | 64 | ## 程序示例 65 | 66 | 为了方便您编程,我们提供了示例程序 `naive.cpp` 与 `interact.cpp`。`naive.cpp` 会使用单个线程依次点开 $N \times N$ 方阵中的每个格子,`interact.cpp` 是一个交互式的扫雷客户端,在启动后您可以在控制台中输入您想点开的格子的坐标,来“手玩”。 67 | 68 | ## 如何准备、测试与提交 69 | 70 | 由于使用了 `futex`,本题仅支持 linux 系统。建议没有 linux 系统的同学使用 比赛提供的 code server(也可以使用虚拟机或 docker)来运行并调试程序。 71 | 72 | 准备步骤如下: 73 | 74 | - (如果你没有用 code server)安装必要的包,包括 make, gcc, g++, zip 75 | - 下载附加文件 `minesweeper_handout.zip`,解压缩,并进入解压缩后的文件夹 76 | - 执行 `make` 77 | - 执行 `python3 generate_example_maps.py`。该程序会调用 `map_generator`,在 `map/` 目录下生成边长从 16 到 65536 不等的地图文件。地图文件的命名规则为 `N_K_seed.map`,其中 `N` 代表地图边长,`K` 代表地图中的雷的个数,`seed` 代表随机数种子。生成的地图文件与最终测试时的地图文件均满足 $K = N^2/8$。请注意最终评测时的地图文件的随机数种子并不是 0。 78 | 79 | 接下来您就可以开始编辑 `answer.cpp` 了。(注:您在 `answer.cpp` 中可以使用 `#include "lib/csapp.h"` 来使用 `csapp.h` 文件。对应的 `csapp.o` 将会在链接时自动加入) 80 | 81 | 如何测试:`make && ./judger answer <地图文件,比如 map/16_32_0.map> [时间限制,单位为秒]` 82 | 83 | 如何提交:将 `answer.cpp` 提交至网站即可。 84 | 85 | 偷偷说一句,`naive.cpp` 可以通过前 8 分。 86 | 87 | ## 评分标准 88 | 89 | 有若干个测试点,每个测试点的分数为: 90 | 91 | $\frac{选手在限定时间内点开的(不同的)非雷格子数 - A \cdot (选手点开的(不同的)雷的个数 - K\cdot0.0002)}{(N^2-K)\times 0.9998} \times 100$,(对 0 取 max,对 100 取 min),其中 $N$ 是地图的边长,$K$ 是地图中雷的总数,$A$ 是常数,详见下方“测试点方案”。 92 | 93 | 换句话说:即使你有 $0.02\%$ 的非雷格子没有点开,并且不小心点开了 $0.02\%$ 的雷,最后也能满分。(不要怕,这很简单!) 94 | 95 | 题目总分为所有测试点的分数之和。 96 | 97 | ## 硬件配置 98 | 99 | 您的程序将与 `game_server` 和 `judger` 一起运行在我们的计算节点上。计算节点的配置为: 100 | 101 | - CPU: [Intel® Xeon® Platinum 8358 Processor](https://www.intel.com/content/www/us/en/products/sku/212282/intel-xeon-platinum-8358-processor-48m-cache-2-60-ghz/specifications.html)。我们会在您的程序运行时提供 16 个核心,但请注意,每个您创建的 Channel,game server 中都有一个独立的线程负责处理该 Channel 中的请求。如果活跃的 Channel 超过 8 个,那么将发生频繁的 context switch,进而在很大程度上影响性能。所以我们建议**活跃的 Channel 数量不要超过八个**。 102 | - 内存:64 GB。`game_server` 与 `judger` 加起来大约会占用 5 GB 内存,操作系统大约会占用 1 GB 内存,剩下的随便用。 103 | 104 | ## 测试点描述 105 | 106 | | 测试点 ID | 每个子测试点的分数 | N(地图边长) | K(雷的数量) | 常数 A | 时间限制(s) | 107 | | --------- | ------------------ | ------------- | ------------- | ------ | ------------- | 108 | | 0 | 8 | 512 | 512*512/8 | 0 | 2 | 109 | | 1 | 16 | 16384 | 16384*16384/8 | 0 | 18 | 110 | | 2 | 14 | 512 | 512*512/8 | 8 | 2 | 111 | | 3 | 16 | 2048 | 2048*2048/8 | 8 | 8 | 112 | | 4 | 30 | 8192 | 8192*8192/8 | 8 | 32 | 113 | | 5 | 30 | 16384 | 16384*16384/8 | 8 | 20 | 114 | | 6 | 32 | 32768 | 32768*32768/8 | 8 | 68 | 115 | | 7 | 44 | 65536 | 65536*65536/8 | 8 | 256 | 116 | 117 | 如果以上数据点都是满分,那么最终再奖励 10 分。满分 200 分。 -------------------------------------------------------------------------------- /answer/expand_with_queue.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | expand_with_queue.cpp - A single threaded solution, with amazing accuracy (~0.01% 3 | mine hit rate) and an acceptable speed. 4 | 5 | For more detail, please refer to `expand_with_queue_mt.cpp`. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | using std::max, std::min; 14 | 15 | #include "csapp.h" 16 | #include "minesweeper_helpers.h" 17 | constexpr int MAXN = 65536; 18 | constexpr int delta_xy[8][2] = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}}; 19 | constexpr int delta_xy_2[16][2] = { 20 | {-2, -2}, {-2, -1}, {-2, 0}, {-2, 1}, {-2, 2}, 21 | {-1, -2}, {-1, 2}, 22 | {0, -2}, {0, 2}, 23 | {1, -2}, {1, 2}, 24 | {2, -2}, {2, -1}, {2, 0}, {2, 1}, {2, 2} 25 | }; 26 | 27 | std::mt19937 rng(0); 28 | 29 | // N: 地图的边长 30 | // K: 地图中的雷的数量 31 | // constant_A: 评分过程中用到的参数 A 32 | long N, K; 33 | int constant_A; 34 | 35 | Channel channel; 36 | 37 | long cnt_empty_opened = 0; 38 | 39 | char* map[MAXN]; 40 | constexpr char MAP_UNKNOWN = -1; 41 | constexpr char MAP_MINE = -2; 42 | 43 | // char* inferred[MAXN]; 44 | 45 | inline bool within_range(int r, int c) { 46 | return r >= 0 && c >= 0 && r < N && c < N; 47 | } 48 | 49 | inline bool map_eq(int r, int c, char x) { 50 | if (r < 0 || c < 0 || r >= N || c >= N) return false; 51 | return map[r][c] == x; 52 | } 53 | inline bool map_is_empty(int r, int c) { 54 | if (r < 0 || c < 0 || r >= N || c >= N) return false; 55 | return map[r][c] >= 0; 56 | } 57 | 58 | int count_adj_unknown(int r, int c) { 59 | return (int)map_eq(r-1, c-1, MAP_UNKNOWN) + map_eq(r-1, c, MAP_UNKNOWN) + map_eq(r-1, c+1, MAP_UNKNOWN) 60 | + map_eq(r, c-1, MAP_UNKNOWN) + map_eq(r, c+1, MAP_UNKNOWN) 61 | + map_eq(r+1, c-1, MAP_UNKNOWN) + map_eq(r+1, c, MAP_UNKNOWN) + map_eq(r+1, c+1, MAP_UNKNOWN); 62 | } 63 | int count_adj_mine(int r, int c) { 64 | return (int)map_eq(r-1, c-1, MAP_MINE) + map_eq(r-1, c, MAP_MINE) + map_eq(r-1, c+1, MAP_MINE) 65 | + map_eq(r, c-1, MAP_MINE) + map_eq(r, c+1, MAP_MINE) 66 | + map_eq(r+1, c-1, MAP_MINE) + map_eq(r+1, c, MAP_MINE) + map_eq(r+1, c+1, MAP_MINE); 67 | } 68 | int count_adj_empty(int r, int c) { 69 | return (int)map_is_empty(r-1, c-1) + map_is_empty(r-1, c) + map_is_empty(r-1, c+1) 70 | + map_is_empty(r, c-1) + map_is_empty(r, c+1) 71 | + map_is_empty(r+1, c-1) + map_is_empty(r+1, c) + map_is_empty(r+1, c+1); 72 | } 73 | 74 | // 如果不是雷,返回 0;如果是雷,返回 1 75 | bool click_and_put_result_no_expand(int r, int c) { 76 | ClickResult result = channel.click_do_not_expand(r, c); 77 | if (result.is_mine) { 78 | map[r][c] = MAP_MINE; 79 | } else { 80 | if (map[r][c] == MAP_UNKNOWN) 81 | cnt_empty_opened += 1; 82 | map[r][c] = (*result.open_grid_pos)[0][2]; 83 | } 84 | return result.is_mine; 85 | } 86 | 87 | 88 | struct QueueItem { 89 | int r, c; 90 | int re_put_cnt; 91 | }; 92 | 93 | void expand(int start_r, int start_c) { 94 | std::deque border; // 所有与当前展开的区域相邻、周围有至少 1 个未知区域的非雷格子 95 | bool first_is_mine = click_and_put_result_no_expand(start_r, start_c); 96 | if (first_is_mine) { 97 | return; 98 | } 99 | border.push_back({start_r, start_c, 0}); 100 | 101 | while (!border.empty()) { 102 | if (cnt_empty_opened == N*N-K) break; 103 | auto [cur_r, cur_c, re_put_cnt] = border.front(); 104 | border.pop_front(); 105 | 106 | // if (inferred[cur_r][cur_c]) continue; 107 | 108 | int num_in_grid = map[cur_r][cur_c]; 109 | assert (num_in_grid >= 0); 110 | int adj_unknown = count_adj_unknown(cur_r, cur_c); 111 | if (!adj_unknown) { 112 | continue; 113 | } 114 | int adj_mine = count_adj_mine(cur_r, cur_c); 115 | if (adj_mine == num_in_grid) { 116 | // inferred[cur_r][cur_c] = true; 117 | // 所有未知的格子都是安全的 118 | for (int k = 0; k < 8; ++k) { 119 | int new_r = cur_r + delta_xy[k][0]; 120 | int new_c = cur_c + delta_xy[k][1]; 121 | if (__builtin_expect(!within_range(new_r, new_c), false)) continue; 122 | if (map[new_r][new_c] != MAP_UNKNOWN) continue; 123 | bool is_mine = click_and_put_result_no_expand(new_r, new_c); 124 | assert(!is_mine); 125 | border.push_front({new_r, new_c, 0}); 126 | } 127 | } else if (adj_unknown+adj_mine == num_in_grid) { 128 | // inferred[cur_r][cur_c] = true; 129 | // 所有未知的格子都是雷 130 | for (int k = 0; k < 8; ++k) { 131 | int new_r = cur_r + delta_xy[k][0]; 132 | int new_c = cur_c + delta_xy[k][1]; 133 | if (__builtin_expect(!within_range(new_r, new_c), false)) continue; 134 | if (map[new_r][new_c] != MAP_UNKNOWN) continue; 135 | map[new_r][new_c] = MAP_MINE; 136 | } 137 | // 拓展一下周围的曼哈顿距离 = 2 的格子 138 | // 不过我发现,如果不加这段代码的话,程序运行会快很多,同时踩雷的次数不会变多很多 139 | // 所以就不加了 140 | // for (int k = 0; k < 16; ++k) { 141 | // int new_r = cur_r + delta_xy_2[k][0]; 142 | // int new_c = cur_c + delta_xy_2[k][1]; 143 | // if (__builtin_expect(!within_range(new_r, new_c), false)) continue; 144 | // if (map[new_r][new_c] < 0) continue; 145 | // // if (inferred[new_r][new_c]) continue; 146 | // if (!count_adj_unknown(new_r, new_c)) continue; 147 | // border.push_front({new_r, new_c, 0}); 148 | // } 149 | } else { 150 | // 暂时推不出来 151 | if (re_put_cnt < 16) 152 | border.push_back({cur_r, cur_c, re_put_cnt+1}); 153 | } 154 | } 155 | 156 | } 157 | 158 | int main() { 159 | minesweeper_init(N, K, constant_A); 160 | 161 | for (int i = 0; i < N; ++i) { 162 | map[i] = (char*)Malloc(N); 163 | memset(map[i], MAP_UNKNOWN, N); 164 | // inferred[i] = (char*)Malloc(N); 165 | // memset(inferred[i], 0, N); 166 | 167 | } 168 | channel = create_channel(); 169 | 170 | // 随机开操 171 | while (cnt_empty_opened <= (N*N-K)*98/100) { 172 | int start_r, start_c; 173 | do { 174 | start_r = rng()&(N-1); 175 | start_c = rng()&(N-1); 176 | } while (map[start_r][start_c] != MAP_UNKNOWN); 177 | expand(start_r, start_c); 178 | } 179 | 180 | // 如果不执行下面这段“按顺序开操”,那么会漏掉一些格子没有点开 181 | // 但是踩雷数量会大幅下降 182 | 183 | // 按顺序开操 184 | for (int start_r = 0; start_r < N; start_r++) { 185 | for (int start_c = 0; start_c < N; start_c++) { 186 | if (map[start_r][start_c] == MAP_UNKNOWN) { 187 | bool infer_is_non_mine __attribute__((unused)) = false, infer_is_mine = false; 188 | for (int k = 0; k < 8; ++k) { 189 | int new_r = start_r + delta_xy[k][0]; 190 | int new_c = start_c + delta_xy[k][1]; 191 | if (__builtin_expect(!within_range(new_r, new_c), false)) continue; 192 | if (map[new_r][new_c] < 0) continue; 193 | int adj_unknown = count_adj_unknown(new_r, new_c); 194 | // assert(adj_unknown); 195 | int adj_mine = count_adj_mine(new_r, new_c); 196 | if (adj_mine == map[new_r][new_c]) { 197 | infer_is_non_mine = true; 198 | break; 199 | } else if (adj_mine + adj_unknown == map[new_r][new_c]) { 200 | infer_is_mine = true; 201 | break; 202 | } 203 | } 204 | if (!infer_is_mine) 205 | expand(start_r, start_c); 206 | } 207 | } 208 | } 209 | 210 | return 0; 211 | } -------------------------------------------------------------------------------- /lib/csapp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * csapp.h - prototypes and definitions for the CS:APP3e book 3 | */ 4 | /* $begin csapp.h */ 5 | #ifndef __CSAPP_H__ 6 | #define __CSAPP_H__ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | /* Default file permissions are DEF_MODE & ~DEF_UMASK */ 33 | /* $begin createmasks */ 34 | #define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH 35 | #define DEF_UMASK S_IWGRP|S_IWOTH 36 | /* $end createmasks */ 37 | 38 | /* Simplifies calls to bind(), connect(), and accept() */ 39 | /* $begin sockaddrdef */ 40 | typedef struct sockaddr SA; 41 | /* $end sockaddrdef */ 42 | 43 | /* Persistent state for the robust I/O (Rio) package */ 44 | /* $begin rio_t */ 45 | #define RIO_BUFSIZE 8192 46 | typedef struct { 47 | int rio_fd; /* Descriptor for this internal buf */ 48 | int rio_cnt; /* Unread bytes in internal buf */ 49 | char *rio_bufptr; /* Next unread byte in internal buf */ 50 | char rio_buf[RIO_BUFSIZE]; /* Internal buffer */ 51 | } rio_t; 52 | /* $end rio_t */ 53 | 54 | /* External variables */ 55 | extern int h_errno; /* Defined by BIND for DNS errors */ 56 | extern char **environ; /* Defined by libc */ 57 | 58 | // used in unix_error() and app_error() 59 | // We print out "prog_name" 60 | extern const char* prog_name; 61 | 62 | /* Misc constants */ 63 | #define MAXLINE 8192 /* Max text line length */ 64 | #define MAXBUF 8192 /* Max I/O buffer size */ 65 | #define LISTENQ 1024 /* Second argument to listen() */ 66 | 67 | /* Our own error-handling functions */ 68 | void unix_error(const char *msg); 69 | void posix_error(int code, const char *msg); 70 | void dns_error(const char *msg); 71 | void getaddrinfo_error(int code, const char *msg); 72 | void app_error(const char *format, ...); 73 | 74 | /* Process control wrappers */ 75 | pid_t Fork(void); 76 | void Execve(const char *filename, char *const argv[], char *const envp[]); 77 | pid_t Wait(int *status); 78 | pid_t Waitpid(pid_t pid, int *iptr, int options); 79 | void Kill(pid_t pid, int signum); 80 | unsigned int Sleep(unsigned int secs); 81 | void Pause(void); 82 | unsigned int Alarm(unsigned int seconds); 83 | void Setpgid(pid_t pid, pid_t pgid); 84 | pid_t Getpgrp(); 85 | 86 | /* Signal wrappers */ 87 | typedef void handler_t(int); 88 | handler_t *Signal(int signum, handler_t *handler); 89 | void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 90 | void Sigemptyset(sigset_t *set); 91 | void Sigfillset(sigset_t *set); 92 | void Sigaddset(sigset_t *set, int signum); 93 | void Sigdelset(sigset_t *set, int signum); 94 | int Sigismember(const sigset_t *set, int signum); 95 | int Sigsuspend(const sigset_t *set); 96 | 97 | /* Sio (Signal-safe I/O) routines */ 98 | size_t sio_strlen(const char s[]); 99 | void sio_ltoa(long v, char s[], int b); 100 | void sio_put(const char s[]); 101 | void sio_put(long v); 102 | void sio_error(const char s[]); 103 | void sio_error(long l); 104 | 105 | /* Unix I/O wrappers */ 106 | int Open(const char *pathname, int flags, mode_t mode); 107 | ssize_t Read(int fd, void *buf, size_t count); 108 | ssize_t Write(int fd, const void *buf, size_t count); 109 | off_t Lseek(int fildes, off_t offset, int whence); 110 | void Close(int fd); 111 | int Select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, 112 | struct timeval *timeout); 113 | int Dup2(int fd1, int fd2); 114 | void Stat(const char *filename, struct stat *buf); 115 | void Fstat(int fd, struct stat *buf) ; 116 | 117 | /* Directory wrappers */ 118 | DIR *Opendir(const char *name); 119 | struct dirent *Readdir(DIR *dirp); 120 | int Closedir(DIR *dirp); 121 | 122 | /* Memory mapping wrappers */ 123 | void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); 124 | void Munmap(void *start, size_t length); 125 | 126 | /* Standard I/O wrappers */ 127 | void Fclose(FILE *fp); 128 | FILE *Fdopen(int fd, const char *type); 129 | char *Fgets(char *ptr, int n, FILE *stream); 130 | FILE *Fopen(const char *filename, const char *mode); 131 | void Fputs(const char *ptr, FILE *stream); 132 | size_t Fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 133 | void Fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); 134 | 135 | /* Dynamic storage allocation wrappers */ 136 | void *Malloc(size_t size); 137 | void *Realloc(void *ptr, size_t size); 138 | void *Calloc(size_t nmemb, size_t size); 139 | void Free(void *ptr); 140 | 141 | /* Sockets interface wrappers */ 142 | int Socket(int domain, int type, int protocol); 143 | void Setsockopt(int s, int level, int optname, const void *optval, int optlen); 144 | void Bind(int sockfd, struct sockaddr *my_addr, int addrlen); 145 | void Listen(int s, int backlog); 146 | int Accept(int s, struct sockaddr *addr, socklen_t *addrlen); 147 | void Connect(int sockfd, struct sockaddr *serv_addr, int addrlen); 148 | 149 | /* Protocol independent wrappers */ 150 | void Getaddrinfo(const char *node, const char *service, 151 | const struct addrinfo *hints, struct addrinfo **res); 152 | void Getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, 153 | size_t hostlen, char *serv, size_t servlen, int flags); 154 | void Freeaddrinfo(struct addrinfo *res); 155 | void Inet_ntop(int af, const void *src, char *dst, socklen_t size); 156 | void Inet_pton(int af, const char *src, void *dst); 157 | 158 | /* DNS wrappers */ 159 | struct hostent *Gethostbyname(const char *name); 160 | struct hostent *Gethostbyaddr(const char *addr, int len, int type); 161 | 162 | /* Pthreads thread control wrappers */ 163 | void Pthread_create(pthread_t *tidp, pthread_attr_t *attrp, 164 | void * (*routine)(void *), void *argp); 165 | void Pthread_join(pthread_t tid, void **thread_return); 166 | void Pthread_cancel(pthread_t tid); 167 | void Pthread_detach(pthread_t tid); 168 | void Pthread_exit(void *retval); 169 | pthread_t Pthread_self(void); 170 | void Pthread_once(pthread_once_t *once_control, void (*init_function)()); 171 | void Pthread_mutex_lock(pthread_mutex_t *mutex); 172 | void Pthread_mutex_unlock(pthread_mutex_t *mutex); 173 | void Pthread_kill(pthread_t tid, int signal); 174 | 175 | /* POSIX semaphore wrappers */ 176 | void Sem_init(sem_t *sem, int pshared, unsigned int value); 177 | void P(sem_t *sem); 178 | void V(sem_t *sem); 179 | 180 | /* Rio (Robust I/O) package */ 181 | ssize_t rio_readn(int fd, void *usrbuf, size_t n); 182 | ssize_t rio_writen(int fd, void *usrbuf, size_t n); 183 | void rio_readinitb(rio_t *rp, int fd); 184 | ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n); 185 | ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); 186 | 187 | /* Wrappers for Rio package */ 188 | ssize_t Rio_readn(int fd, void *usrbuf, size_t n); 189 | void Rio_writen(int fd, void *usrbuf, size_t n); 190 | void Rio_readinitb(rio_t *rp, int fd); 191 | ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n); 192 | ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); 193 | 194 | /* Reentrant protocol-independent client/server helpers */ 195 | int open_clientfd(char *hostname, char *port); 196 | int open_listenfd(char *port); 197 | 198 | /* Wrappers for reentrant protocol-independent client/server helpers */ 199 | int Open_clientfd(char *hostname, char *port); 200 | int Open_listenfd(char *port); 201 | 202 | 203 | #endif /* __CSAPP_H__ */ 204 | /* $end csapp.h */ -------------------------------------------------------------------------------- /answer/expand_with_queue_mt.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | expand_with_queue_mt.cpp - The multithreaded version of `expand_with_queue`. 3 | 4 | This is the standard solution (STD). This solution achieves an amazing 5 | accuracy (~0.01% mine hit rate) and a fascinating speed. (Can solve map 6 | with a side length of 65536 in ~120s on my 5800H). 7 | 8 | Idea: we can see each thread as a "miner". It starts with a non-mine 9 | grid (by simply clicking until we find one) and tries its best to expand the 10 | "safe region" (namely, the non-mine connected component containing the starting grid). 11 | In detail, it maintains the "border" of the current "safe region" with 12 | a queue. The "border" includes all grids that: 13 | - In the current "safe region" 14 | - There are at least one unknown grid in its neighbour 15 | For more details, please refer to `expand()`. 16 | 17 | What to do when two threads (miners) "meet" at one grid (that is, their 18 | "safe region"s overlap): We create another N*N array, `marker`. When a thread 19 | wants to put a particular grid (r, c) into its queue, it first checks whether 20 | `marker[r][c] == 0`. If the answer is YES, it marks `marker[r][c]` as its 21 | own thread id and put the grid into the queue. If the answer is NO, which 22 | means that the grid has been "snatched" by another worker, it just skips 23 | the grid. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | using std::max, std::min, std::atomic; 34 | 35 | #include "csapp.h" 36 | #include "minesweeper_helpers.h" 37 | 38 | constexpr int NUM_WORKER_THREAD = 8; 39 | 40 | constexpr int MAXN = 65536; 41 | constexpr int delta_xy[8][2] = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}}; 42 | 43 | std::mt19937 rng(0); 44 | 45 | // N: 地图的边长 46 | // K: 地图中的雷的数量 47 | // constant_A: 评分过程中用到的参数 A 48 | long N, K; 49 | int constant_A; 50 | 51 | // We use "approximate counting" to avoid data race while achieving a better performance 52 | // That is, each thread maintains a "local counter", and there is one "global 53 | // counter" (`cnt_empty_opened`). A threads adds to its own local counter by 54 | // default, and when the local counter reaches `APPROX_COUNTING_THRES`, the thread 55 | // adds `APPROX_COUNTING_THRES` to the global counter and sets the local counter 56 | // to 0. 57 | // This is the technique called "Approximate Counting", which balances accuracy 58 | // and performance. 59 | atomic cnt_empty_opened = 0; 60 | constexpr int APPROX_COUNTING_THRES = 32; 61 | 62 | // Variables and functions for maintaining the map 63 | char* map[MAXN]; 64 | constexpr char MAP_UNKNOWN = -1; 65 | constexpr char MAP_MINE = -2; 66 | 67 | inline bool within_range(int r, int c) { 68 | return r >= 0 && c >= 0 && r < N && c < N; 69 | } 70 | 71 | inline bool map_eq(int r, int c, char x) { 72 | if (r < 0 || c < 0 || r >= N || c >= N) return false; 73 | return map[r][c] == x; 74 | } 75 | inline bool map_is_empty(int r, int c) { 76 | if (r < 0 || c < 0 || r >= N || c >= N) return false; 77 | return map[r][c] >= 0; 78 | } 79 | 80 | int count_adj_unknown(int r, int c) { 81 | return (int)map_eq(r-1, c-1, MAP_UNKNOWN) + map_eq(r-1, c, MAP_UNKNOWN) + map_eq(r-1, c+1, MAP_UNKNOWN) 82 | + map_eq(r, c-1, MAP_UNKNOWN) + map_eq(r, c+1, MAP_UNKNOWN) 83 | + map_eq(r+1, c-1, MAP_UNKNOWN) + map_eq(r+1, c, MAP_UNKNOWN) + map_eq(r+1, c+1, MAP_UNKNOWN); 84 | } 85 | int count_adj_mine(int r, int c) { 86 | return (int)map_eq(r-1, c-1, MAP_MINE) + map_eq(r-1, c, MAP_MINE) + map_eq(r-1, c+1, MAP_MINE) 87 | + map_eq(r, c-1, MAP_MINE) + map_eq(r, c+1, MAP_MINE) 88 | + map_eq(r+1, c-1, MAP_MINE) + map_eq(r+1, c, MAP_MINE) + map_eq(r+1, c+1, MAP_MINE); 89 | } 90 | int count_adj_empty(int r, int c) { 91 | return (int)map_is_empty(r-1, c-1) + map_is_empty(r-1, c) + map_is_empty(r-1, c+1) 92 | + map_is_empty(r, c-1) + map_is_empty(r, c+1) 93 | + map_is_empty(r+1, c-1) + map_is_empty(r+1, c) + map_is_empty(r+1, c+1); 94 | } 95 | 96 | // 如果不是雷,返回 0;如果是雷,返回 1 97 | bool click_and_put_result_no_expand(Channel &channel, int r, int c, long &local_cnt_empty_opened) { 98 | ClickResult result = channel.click_do_not_expand(r, c); 99 | if (result.is_mine) { 100 | map[r][c] = MAP_MINE; 101 | } else { 102 | if (map[r][c] == MAP_UNKNOWN) { 103 | local_cnt_empty_opened++; 104 | if (local_cnt_empty_opened == APPROX_COUNTING_THRES) { 105 | cnt_empty_opened += APPROX_COUNTING_THRES; 106 | local_cnt_empty_opened = 0; 107 | } 108 | map[r][c] = (*result.open_grid_pos)[0][2]; 109 | } 110 | } 111 | return result.is_mine; 112 | } 113 | 114 | 115 | // 原则:每个数字格子最多被一个线程入队 116 | // 所以我们用 marker 记录第一个点开这个格子的线程的 TID 117 | char* marker[MAXN]; 118 | 119 | struct QueueItem { 120 | int r, c; 121 | int re_put_cnt; 122 | }; 123 | 124 | void expand(int start_r, int start_c, int thread_id, Channel &channel, long &local_cnt_empty_opened) { 125 | std::deque border; 126 | bool first_is_mine = click_and_put_result_no_expand(channel, start_r, start_c, local_cnt_empty_opened); 127 | if (first_is_mine) { 128 | return; 129 | } 130 | border.clear(); 131 | border.push_back({start_r, start_c, 0}); 132 | 133 | while (!border.empty()) { 134 | if (cnt_empty_opened == N*N-K) break; 135 | 136 | auto [cur_r, cur_c, re_put_cnt] = border.front(); 137 | border.pop_front(); 138 | 139 | int num_in_grid = map[cur_r][cur_c]; 140 | 141 | int adj_unknown = count_adj_unknown(cur_r, cur_c); 142 | if (!adj_unknown) { 143 | // 它周围已经没有未知的格子了 144 | continue; 145 | } 146 | 147 | int adj_mine = count_adj_mine(cur_r, cur_c); 148 | if (adj_mine == num_in_grid) { 149 | // 所有未知的格子都是安全的 150 | for (int k = 0; k < 8; ++k) { 151 | int new_r = cur_r + delta_xy[k][0]; 152 | int new_c = cur_c + delta_xy[k][1]; 153 | if (__builtin_expect(!within_range(new_r, new_c), false)) continue; 154 | if (map[new_r][new_c] != MAP_UNKNOWN) continue; 155 | if (marker[new_r][new_c] != 0 && marker[new_r][new_c] != thread_id) continue; 156 | marker[new_r][new_c] = thread_id; 157 | bool is_mine = click_and_put_result_no_expand(channel, new_r, new_c, local_cnt_empty_opened); 158 | assert(!is_mine); 159 | border.push_front({new_r, new_c, 0}); 160 | } 161 | } else if (adj_unknown+adj_mine == num_in_grid) { 162 | // 所有未知的格子都是雷 163 | for (int k = 0; k < 8; ++k) { 164 | int new_r = cur_r + delta_xy[k][0]; 165 | int new_c = cur_c + delta_xy[k][1]; 166 | if (__builtin_expect(!within_range(new_r, new_c), false)) continue; 167 | if (map[new_r][new_c] != MAP_UNKNOWN) continue; 168 | map[new_r][new_c] = MAP_MINE; 169 | } 170 | } else { 171 | // 暂时推不出来,把这个格子放到队列的最后。 172 | // 每个格子最多被如此重新入队 16 次。 173 | if (re_put_cnt < 16) { 174 | if (marker[cur_r][cur_c] != 0 && marker[cur_r][cur_c] != thread_id) continue; 175 | marker[cur_r][cur_c] = thread_id; 176 | border.push_back({cur_r, cur_c, re_put_cnt+1}); 177 | } 178 | } 179 | } 180 | } 181 | 182 | 183 | void* thread_routine(void* arg) { 184 | // 随机开操 185 | Channel channel = create_channel(); 186 | int thread_id = (int)(long)arg; 187 | long local_cnt_empty_opened = 0; 188 | const long break_thres = (N*N-K)*95/100; 189 | while (cnt_empty_opened <= break_thres) { 190 | int start_r, start_c; 191 | do { 192 | start_r = rng()&(N-1); 193 | start_c = rng()&(N-1); 194 | if (cnt_empty_opened > break_thres) { 195 | return NULL; 196 | } 197 | } while (map[start_r][start_c] != MAP_UNKNOWN); 198 | expand(start_r, start_c, thread_id, channel, local_cnt_empty_opened); 199 | cnt_empty_opened += local_cnt_empty_opened; 200 | local_cnt_empty_opened = 0; 201 | } 202 | return NULL; 203 | } 204 | 205 | int main() { 206 | minesweeper_init(N, K, constant_A); 207 | 208 | for (int i = 0; i < N; ++i) { 209 | map[i] = (char*)Malloc(N); 210 | memset(map[i], MAP_UNKNOWN, N); 211 | marker[i] = (char*)Calloc(N, 1); 212 | } 213 | 214 | pthread_t tids[NUM_WORKER_THREAD]; 215 | for (int thread_id = 1; thread_id <= NUM_WORKER_THREAD; ++thread_id) { 216 | Pthread_create(tids+thread_id-1, NULL, thread_routine, (void*)(long)thread_id); 217 | } 218 | for (int i = 0; i < NUM_WORKER_THREAD; ++i) { 219 | Pthread_join(tids[i], NULL); 220 | } 221 | 222 | // 如果不执行下面这段“按顺序开操”,那么会漏掉一些格子没有点开 223 | // 但是踩雷数量会大幅下降 224 | // 不过不论这里加不加 return 0,这份程序都可以满分 225 | // return 0; 226 | 227 | // printf("Enter phase 2\n"); 228 | 229 | // 按顺序开操 230 | Channel channel = create_channel(); 231 | long local_cnt_empty_opened = 0; 232 | for (int start_r = 0; start_r < N; start_r++) { 233 | for (int start_c = 0; start_c < N; start_c++) { 234 | if (map[start_r][start_c] == MAP_UNKNOWN) { 235 | bool infer_is_non_mine __attribute__((unused)) = false, infer_is_mine = false; 236 | for (int k = 0; k < 8; ++k) { 237 | int new_r = start_r + delta_xy[k][0]; 238 | int new_c = start_c + delta_xy[k][1]; 239 | if (__builtin_expect(!within_range(new_r, new_c), false)) continue; 240 | if (map[new_r][new_c] < 0) continue; 241 | int adj_unknown = count_adj_unknown(new_r, new_c); 242 | // assert(adj_unknown); 243 | int adj_mine = count_adj_mine(new_r, new_c); 244 | if (adj_mine == map[new_r][new_c]) { 245 | infer_is_non_mine = true; 246 | break; 247 | } else if (adj_mine + adj_unknown == map[new_r][new_c]) { 248 | infer_is_mine = true; 249 | break; 250 | } 251 | } 252 | // 如果我们不知道这个格子一定是雷,我们就尝试从这个点开始 expand 253 | if (!infer_is_mine) 254 | expand(start_r, start_c, 100, channel, local_cnt_empty_opened); 255 | } 256 | } 257 | } 258 | 259 | return 0; 260 | } -------------------------------------------------------------------------------- /statement/en-us.md: -------------------------------------------------------------------------------- 1 | ## Background 2 | 3 | You like playing the game "Minesweeper" very much. 4 | 5 | In the game "Minesweeper", you are facing a matrix containing $N \times N$ squares, and each square may have a mine or no mine. 6 | 7 | Your basic operation is called "click". Every time, you can choose a square and "click" it, if it is a mine, then your points will be deducted, if it is not a mine, then you will get one point, and a number will show up on the square, indicating how many mines (0~8) are there in the surrounding eight squares (up, down, left, right, upper left, lower left, upper right, lower right). If the number in the clicked square is 0 (this means that all the eight squares around this square are not mines), then the surrounding squares will also be automatically "clicked" (this process can be recursive, that is, if the number in the new square is also 0, then the eight surrounding squares will also be automatically "clicked". We call it "indirect click", corresponding to the square directly clicked by the player, called "direct click"). When the player voluntarily quits, or the time runs out, the game ends. 8 | 9 | Please note that there are some differences between the minesweeper game for this question and the standard minesweeper game in the Windows system: 10 | 11 | - In the standard minesweeper game, clicking on a square containing mines will cause the game to fail immediately, while in our game, your score will be deduced, but the game will continue. 12 | - When all non-mine squares are clicked, the game will not end automatically, and the player's program needs to actively exit. 13 | - Every time you "click" a square containing number 0, your program will receive all the squares with the number 0 in the connected block containing the clicked square and the squares with numbers other than 0 at the edge, even if these squares have already been clicked before. This is described in detail later. 14 | 15 | Here is an example of the "Minesweeper" game on the KDE desktop system (a small red flag means that there are mines in the square, and an empty square means that the number in the square is 0): 16 | 17 | ![Quoted from https://commons.wikimedia.org/wiki/File:Minesweeper_end_Kmines.png](https://hpcgame.pku.edu.cn/oss/images/Minesweeper_end_Kmines.png) 18 | 19 | 20 | After practicing day and night, the $30 \times 16$ map size of the advanced version of Minesweeper is already a piece of cake for you. So, you decide to play something more exciting. What about, minesweeper for $65536 \times 65535$? 21 | 22 | Of course, minesweeping on such a large scale cannot be done by hand. So, you plan to integrate what you have learned all your life into a program, and let this program solve the game. 23 | 24 | ## Description 25 | 26 | You need to write a (possibly parallel) program to interact with the minesweeper game server we provide. 27 | 28 | You can use the following library functions to interact with the game server: 29 | 30 | - `void minesweeper_init(int &N, int &K, int &constant_A);` Before the program starts, please call this function to initialize the program. This function will store the side length $N$ of the map in the variable `N`, the number of mines $K$ in the map in the variable `K`, and the scoring parameter $A$ (see below) in the variable `constant_A`. 31 | 32 | - `Channel create_channel(void);` Please use this function to create a `Channel` object for communicating with the game server. Ensure that different `Channel` do not interfere with each other, but if two processes/threads operate the same `Channel` at the same time, you will be blown up. So it is recommended to open a `Channel` for each process/thread separately. 33 | 34 | You can create up to 1024 Channels with this function, but I recommend that you try to keep no more than 8 Channels active (calling `click()` repeatedly). After all, for each Channel you create, a separate thread in the game server is responsible for handling requests in that Channel. We only provide 16 cores during evaluation (note: there is no hyperthreading), so if there are more than 8 active channels, frequent context switches will occur, which greatly affects performance. 35 | 36 | - `ClickResult Channel::click(int r, int c, bool skip_when_reopen);` This is a member function of `Channel`, which represents the operation of "clicking on the square corresponding to the position of $(r, c)$", $0 \le r, c < n$. 37 | 38 | See `minesweeper_helpers.h` for the definition of `ClickResult` class and its member variables. 39 | 40 | Every time you "click" a square containing the number 0, your program will receive all the squares with the number 0 in the connected block where the clicked square is located and the squares with numbers other than 0 at the edge, even if these squares are already been clicked. For example: Suppose the map is as follows (X represents mines, the coordinates of the upper left corner are $(0, 0)$, the coordinates of the upper right corner are $(0, 2)$, and the coordinates of the lower right corner are $(2, 2)$): 41 | 42 | ``` 43 | 0 1 X 44 | 1 2 1 45 | X 1 0 46 | ``` 47 | 48 | Then when calling `click(0, 0, false)`, the clicked square contains $(0, 0), (0, 1), (1, 0), (1, 1)$; then if you call `click(2, 2, false)`, then the clicked square contains $(1, 1), (1, 2), (2, 1), (2, 2)$. Please note that in both calls, the $(1, 1)$ box is clicked. Corresponding to the program, that is $(1, 1)$ This point appears in the `open_grid_pos` array of `ClickResult` returned by `click()` twice. 49 | 50 | If `skip_when_open` is `true`, then if the square $(r, c)$ has been clicked before (whether it is opened by this `Channel` or other `Channel`, and whether directly clicked or indirectly clicked), then the `click` function will immediately return a `ClickResult` with `is_skipped=true` (Note: The main purpose of providing this operation is to save time). Please note that this option guarantees that if `is_skipped=true` is returned, then the square $(r, c)$ must have been clicked before; however, if two threads click on the same square approximately at the same time (or both indirectly click on the opponent's square), then there is a certain probability that neither of them will skip. In other words, there are some data race issues here. 51 | 52 | The complexity of this function is: $\Theta({\small\text{The number of squares opened}})$. If `skip_when_open=true` and the square was opened before, or the square contains mines, then the complexity of the function is $\Theta(1)$. 53 | 54 | - `ClickResult Channel::click_do_not_expand(int r, int c);` This is a member function of `Channel`, which means "click to open the square corresponding to the position of $(r, c)$, and do not click indirectly". 55 | 56 | Unlike the `click()` function above, this function does not do indirect clicks. In other words, assuming that the number in the square $(r, c)$ is $0$, and the numbers in the surrounding squares are not $0$, then `click(r, c)` will click and return $(r, c)$ and its surrounding squares, while `click_do_not_expand(r, c)` will only click and return the square $(r, c)$. 57 | 58 | The complexity of this function is always $\Theta(1)$. 59 | 60 | (Note: The author of the question found after writing the standard solution that this function seems to be more commonly used than the `click()` above...) 61 | 62 | These functions are defined in `minesweeper_helpers.h`. You can add `#include "minesweeper_helpers.h"` at the beginning of your program to use these functions. 63 | 64 | ## Example Solution 65 | 66 | For your programming convenience, we provide sample programs `naive.cpp` and `interact.cpp`. `naive.cpp` will use a single thread to open each square in the $N \times N$ square matrix in turn. `interact.cpp` is an interactive minesweeper client. After startup, you can enter the coordinates of the square you want to open, which allows you to play the game by your own hands. 67 | 68 | ## How to Prepare, Test and Submit 69 | 70 | Due to the use of `futex`, this question only supports linux systems. It is recommended that students who do not have a linux system use the code server (or virtual machine or docker) provided by the contest organizer to run and debug the program. 71 | 72 | The preparation steps are as follows: 73 | 74 | - (if you don't use code server) install necessary packages, including make, gcc, g++, zip 75 | - Download the additional file `minesweeper_handout.zip`, unzip it, and switch to the unzipped folder 76 | - Execute `make` 77 | - Execute `python3 generate_example_maps.py`. This program will call `map_generator` to generate map files with side lengths ranging from 16 to 65536 and store them in the `map/` directory. The naming rule of the map file is `N_K_seed.map`, where `N` represents the side length of the map, `K` represents the number of mines in the map, and `seed` represents the random number seed. Both the generated map file and the map file in the final test satisfy $K = N^2/8$. Please note that the random number seed of the map file at the time of final evaluation is not 0. 78 | 79 | You can then start editing `answer.cpp`. (Note: You can use `#include "lib/csapp.h"` in `answer.cpp` to use `csapp.h` file. The corresponding `csapp.o` will be added automatically when linking) 80 | 81 | How to test: `make && ./judger answer [time limit in seconds]` 82 | 83 | How to submit: Submit `answer.cpp` to the website. 84 | 85 | By the way, `naive.cpp` can pass the first 8 points. 86 | 87 | ## Grading 88 | 89 | There are several test points, and the score of each test point is: 90 | 91 | $\frac{\text{Number of (different) non-mine squares opened by the player within the time limit} - A \cdot \text{(Number of (different) mines opened by the player} - K\cdot0.0002)}{(N^2 -K)\times 0.9998} \times 100$, (max for 0, min for 100), where $N$ is the side length of the map, $K$ is the total number of mines in the map and $A$ is a constant. See "Test Point Scheme" below for details. 92 | 93 | In other words: Even if you forgot to click $0.02\%$ of non-mine squares, and you accidentally clicked $0.02\%$ of mines, you can still get full marks in the end. (Be brave! It's easy!) 94 | 95 | The total score of the problem is the sum of the scores of all test points. 96 | 97 | ## Hardware Configuration 98 | 99 | Your program will run on our compute nodes together with `game_server` and `judger`. The configuration of the compute nodes is: 100 | 101 | - CPU: [Intel® Xeon® Platinum 8358 Processor](https://www.intel.com/content/www/us/en/products/sku/212282/intel-xeon-platinum-8358-processor-48m-cache-2-60-ghz/specifications.html). We will provide 16 cores when your program is running, but please note that for each Channel you create, a separate thread in the game server is responsible for processing requests in that Channel. If there are more than 8 active Channels, frequent context switches will occur, which will greatly affect performance. So we recommend that the number of **active channels should not exceed eight**. 102 | - Memory: 64GB. `game_server` and `judger` will take about 5 GB of memory together, the operating system will take up about 1 GB of memory, and use the rest as you like. 103 | 104 | ## Test Point Description 105 | 106 | | Test Point ID | Score | N | K | Constant A | Time Limit (s) | 107 | | --------- | ------------------ | ------------- | ------------- | ------ | ------------- | 108 | | 0 | 8 | 512 | 512*512/8 | 0 | 2 | 109 | | 1 | 16 | 16384 | 16384*16384/8 | 0 | 18 | 110 | | 2 | 14 | 512 | 512*512/8 | 8 | 2 | 111 | | 3 | 16 | 2048 | 2048*2048/8 | 8 | 8 | 112 | | 4 | 30 | 8192 | 8192*8192/8 | 8 | 32 | 113 | | 5 | 30 | 16384 | 16384*16384/8 | 8 | 20 | 114 | | 6 | 32 | 32768 | 32768*32768/8 | 8 | 68 | 115 | | 7 | 44 | 65536 | 65536*65536/8 | 8 | 256 | 116 | 117 | If you get full score in all test points, you will be awarded with 10 points. The full score is 200. -------------------------------------------------------------------------------- /judger.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | judger - The judger. 3 | 4 | It needs: 1) Your(player's) program (in executable file), 2) The game 5 | server (in executable file), 3) The map file for mine-sweeping. 6 | Then it sets up the connection between player's program and the 7 | game server and then starts the game server. When time is up, it notifies 8 | the game server, which sends the result to the judger. The judger then calculate 9 | the player's score. 10 | 11 | Usage: ./judger [constant A (default: 8)] [time_limit (In seconds, default: +inf)] [path/to/game/server (Default: ./game_server)] 12 | 13 | Pipes: 14 | When judging, the three programs (player, game server,judger) are connected by pipes as follow: 15 | 16 | Player (pl) Game Server (gs) Judger (ju) 17 | stdin stdin stdin 18 | stdout stdout stdout 19 | stderr stderr stderr 20 | fd_to_gs ---------------> fd_from_pl 21 | fd_from_gs <------------- fd_to_pl 22 | fd_to_ju -------> fd_from_gs 23 | fd_from_ju <----- fd_to_gs 24 | 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "lib/wrappers.h" 34 | #include "lib/log.h" 35 | #include "lib/common.h" 36 | #include "lib/shm.h" 37 | 38 | void usage(char* prog_name) { 39 | printf("Usage: %s [constant A] [time_limit (In seconds, default: +inf)] [path/to/game/server (Default: ./game_server)]\n", prog_name); 40 | exit(0); 41 | } 42 | 43 | // Helper functions for blocking all signals / restore to default signal 44 | sigset_t sigset_prev; 45 | void block_all_signals() { 46 | sigset_t sigset_block_all; 47 | Sigfillset(&sigset_block_all); 48 | Sigprocmask(SIG_BLOCK, &sigset_block_all, &sigset_prev); 49 | } 50 | void restore_prev_blockset() { 51 | Sigprocmask(SIG_SETMASK, &sigset_prev, NULL); 52 | } 53 | 54 | // create_pipe - Create a pipe 55 | // It guarantee that the result fds >= 100 56 | void create_pipe(int &read_fd, int &write_fd) { 57 | static int current_fd = 102; 58 | // generate a pipe 59 | int t_read_fd, t_write_fd; 60 | Pipe(t_read_fd, t_write_fd); 61 | // create read fd 62 | read_fd = current_fd; 63 | Dup2(t_read_fd, read_fd); 64 | ++current_fd; 65 | // create write fd 66 | write_fd = current_fd; 67 | Dup2(t_write_fd, write_fd); 68 | ++current_fd; 69 | // close old fds 70 | Close(t_read_fd); 71 | Close(t_write_fd); 72 | } 73 | 74 | char* player_path; // path to the player's program (executable file) 75 | char* map_file_path; // path to the map 76 | char* game_server_path; // path to the game server (executable file) 77 | int time_limit; // time limit, in seconds 78 | int constant_A; 79 | 80 | char shm_name[64] = ""; // name of the shared memory region 81 | 82 | int fd_pl_to_gs, fd_gs_from_pl; 83 | int fd_pl_from_gs, fd_gs_to_pl; 84 | int fd_gs_to_ju, fd_ju_from_gs; 85 | int fd_gs_from_ju, fd_ju_to_gs; 86 | 87 | pid_t game_server_pid, player_pid; 88 | 89 | void make_sure_file_exists(const char* path, const char* file_description) { 90 | if (!std::filesystem::exists(path)) { 91 | app_error("Error: file %s (%s) does not exists.\n", path, file_description); 92 | exit(1); 93 | } 94 | } 95 | 96 | void make_sure_file_is_executable(const char* path, const char* file_description) { 97 | if (access(path, X_OK) != 0) { 98 | app_error("Error: file %s (%s) is not executable.\n", path, file_description); 99 | exit(1); 100 | } 101 | } 102 | 103 | void read_result_from_game_server_and_report(); 104 | 105 | // The SIGCHLD signal handler 106 | // Invoked when the player's program / the game server exits. 107 | void sigchld_handler(int _) { 108 | int old_errno = errno; 109 | block_all_signals(); 110 | 111 | int status; 112 | pid_t pid; 113 | while ((pid=Waitpid(-1, &status, WNOHANG)) != 0) { 114 | if (pid == player_pid) { 115 | // The player's program exits 116 | int exit_status = WEXITSTATUS(status); 117 | if (exit_status != 0) { 118 | sio_log("Warning: The player's program exits with a non-zero exit code: "); 119 | sio_put(exit_status); sio_put("\n"); 120 | } 121 | if (WIFSIGNALED(status)) { 122 | sio_log("Warning: The player's program is killed by a signal.\n"); 123 | sio_log("The signal causing the player's program to terminate: "); 124 | sio_put(WTERMSIG(status)); 125 | sio_put("\n"); 126 | } 127 | read_result_from_game_server_and_report(); 128 | } else if (pid == game_server_pid) { 129 | // The game server exits 130 | // This should never happend 131 | int exit_status = WEXITSTATUS(status); 132 | sio_log("Error: The game server exits before the judger\n"); 133 | sio_log("In my design, this should never happen.\n"); 134 | sio_log("This is most probably because that the game server crashes for some reason.\n"); 135 | sio_log("Exit status of the game server: "); 136 | sio_put(exit_status); sio_put("\n"); 137 | if (WIFSIGNALED(status)) { 138 | sio_log("The signal causing the game server to terminate: "); 139 | sio_put(WTERMSIG(status)); 140 | sio_put("\n"); 141 | } 142 | sio_log(this_is_a_bug_str); 143 | if (shm_name[0]) { 144 | Shm_unlink(shm_name); 145 | } 146 | exit(1); 147 | } else { 148 | // The ... em ... well, I don't know what to say 149 | sio_log("Error! In `sigchld_handler` in `judger.cpp`, some child with an unexpected PID exits.\n"); 150 | sio_log("\tInfo: game_server_pid: "); sio_put(game_server_pid); 151 | sio_put(" player_pid: "); sio_put(player_pid); 152 | sio_put(" pid of the exited child: "); sio_put(pid); 153 | sio_put("\n"); 154 | sio_log(this_is_a_bug_str); 155 | if (shm_name[0]) { 156 | Shm_unlink(shm_name); 157 | } 158 | exit(1); 159 | } 160 | } 161 | 162 | restore_prev_blockset(); 163 | errno = old_errno; 164 | } 165 | 166 | // The SIGPIPE signal handler 167 | // Invoked when we try to write something to a closed pipe. 168 | // In ideal situations, we receive SIGCHLD before any SIGPIPE, and in our logic, 169 | // when we receive SIGCHLD, the whole judging prodecure is going to finish, 170 | // which means that we won't write anything to any pipes. 171 | // So receiving SIGPIPE means there is some bug inside the judger. 172 | void sigpipe_handler(int _) { 173 | block_all_signals(); 174 | sio_log("Warning: Broken pipe.\n"); 175 | sio_log("This means that there are some bugs in the judger.\n"); 176 | sio_log(this_is_a_bug_str); 177 | if (shm_name[0]) { 178 | Shm_unlink(shm_name); 179 | } 180 | exit(1); 181 | } 182 | 183 | // The SIGALRM signal handler 184 | // Invoked when time is up. 185 | void sigalrm_handler(int _) { 186 | block_all_signals(); 187 | sio_log("Time is up. Killing player's program and reading result from the game server.\n"); 188 | kill(player_pid, SIGKILL); 189 | read_result_from_game_server_and_report(); 190 | } 191 | 192 | // The SIGINT signal handler 193 | // Invoked when the user presses ctrl-C 194 | void sigint_handler(int _) { 195 | if (shm_name[0]) { 196 | Shm_unlink(shm_name); 197 | } 198 | exit(0); 199 | } 200 | 201 | // Create a shared memory region, consisting MAX_CHANNEL*CHANNEL_MEMORY_REGION_SIZE = SHM_SIZE bytes 202 | void create_shared_memory_region() { 203 | // Generate a random shm name 204 | generate_random_shm_name(shm_name); 205 | // Create the fd pointing to the shared memory region 206 | int mem_fd = Shm_open(shm_name, O_CREAT | O_TRUNC | O_RDWR, S_IRWXU); 207 | // Truncate the memory region 208 | Ftruncate(mem_fd, TOTAL_SHM_SIZE); 209 | } 210 | 211 | void create_game_server() { 212 | if ((game_server_pid = Fork()) == 0) { 213 | // I am the child 214 | // Close unnecessary file descriptors 215 | Close(fd_pl_from_gs); 216 | Close(fd_pl_to_gs); 217 | Close(fd_ju_from_gs); 218 | Close(fd_ju_to_gs); 219 | 220 | // Set env variables 221 | char buf[16]; 222 | sprintf(buf, "%d", fd_gs_to_pl); 223 | Setenv("MINESWEEPER_FD_GS_TO_PL", buf, true); 224 | sprintf(buf, "%d", fd_gs_from_pl); 225 | Setenv("MINESWEEPER_FD_GS_FROM_PL", buf, true); 226 | sprintf(buf, "%d", fd_gs_to_ju); 227 | Setenv("MINESWEEPER_FD_GS_TO_JU", buf, true); 228 | sprintf(buf, "%d", fd_gs_from_ju); 229 | Setenv("MINESWEEPER_FD_GS_FROM_JU", buf, true); 230 | Setenv("MINESWEEPER_MAP_FILE_PATH", map_file_path, true); 231 | Setenv("MINESWEEPER_SHM_NAME", shm_name, true); 232 | Setenv("MINESWEEPER_LAUNCHED_BY_JUDGER", "1", true); 233 | 234 | // Reset signal handlers 235 | Signal(SIGCHLD, SIG_DFL); 236 | Signal(SIGPIPE, SIG_DFL); 237 | Signal(SIGALRM, SIG_DFL); 238 | Signal(SIGINT, SIG_DFL); 239 | 240 | log("Starting game server...\n"); 241 | 242 | // Exec 243 | Execl(game_server_path, game_server_path, NULL); 244 | } else { 245 | // I am the parent (the judger) 246 | Close(fd_gs_from_ju); 247 | Close(fd_gs_from_pl); 248 | Close(fd_gs_to_ju); 249 | Close(fd_gs_to_pl); 250 | } 251 | } 252 | 253 | void create_player() { 254 | if ((player_pid = Fork()) == 0) { 255 | // I am the child 256 | // Close unnecessary file descriptors 257 | Close(fd_ju_from_gs); 258 | Close(fd_ju_to_gs); 259 | 260 | // Set env variables 261 | char buf[16]; 262 | sprintf(buf, "%d", fd_pl_to_gs); 263 | Setenv("MINESWEEPER_FD_PL_TO_GS", buf, true); 264 | sprintf(buf, "%d", fd_pl_from_gs); 265 | Setenv("MINESWEEPER_FD_PL_FROM_GS", buf, true); 266 | sprintf(buf, "%d", constant_A); 267 | Setenv("MINESWEEPER_CONSTANT_A", buf, true); 268 | Setenv("MINESWEEPER_SHM_NAME", shm_name, true); 269 | Setenv("MINESWEEPER_LAUNCHED_BY_JUDGER", "1", true); 270 | 271 | // Reset signal handlers 272 | Signal(SIGCHLD, SIG_DFL); 273 | Signal(SIGPIPE, SIG_DFL); 274 | Signal(SIGALRM, SIG_DFL); 275 | Signal(SIGINT, SIG_DFL); 276 | 277 | log("Starting player's program...\n"); 278 | 279 | // Exec 280 | Execl(player_path, player_path, NULL); 281 | } else { 282 | // I am the parent (the judger) 283 | Close(fd_pl_from_gs); 284 | Close(fd_pl_to_gs); 285 | } 286 | } 287 | 288 | // read_result_from_game_server - Send character 'F' to the game server and 289 | // read the result from game server (via fd_ju_from_gs), print it out, report it 290 | // to the grader and exit 291 | void read_result_from_game_server_and_report() { 292 | // Send "F" to the game server 293 | char c = 'F'; 294 | Write(fd_ju_to_gs, &c, 1); 295 | // Read the response 296 | char buf[128]; 297 | Read(fd_ju_from_gs, buf, 128); 298 | // Parse the response 299 | int status; 300 | long N, K; 301 | long cnt_non_mine, cnt_is_mine; 302 | assert(sscanf(buf, "%d %ld %ld %ld %ld", 303 | &status, &N, &K, &cnt_non_mine, &cnt_is_mine) == 5); 304 | // Print it out 305 | log("Result:\n"); 306 | log("点开的非雷格子: %ld/%ld (%.4f%%)\n", 307 | cnt_non_mine, N*N-K, (double)cnt_non_mine/(N*N-K)*100); 308 | log("点开的雷: %ld/%ld (%.4f%%)\n", 309 | cnt_is_mine, K, (double)cnt_is_mine/K*100); 310 | // Calculate the score 311 | double score = (double)(cnt_non_mine - constant_A*(cnt_is_mine - K*0.0002)) / ((N*N-K)*0.9998) * 100; 312 | if (score > 100) score = 100; 313 | if (score < 0) score = 0; 314 | log("最终得分:%.2f 分。%s\n", score, score == 100 ? "牛逼!" : ""); 315 | // Exit 316 | if (shm_name[0]) { 317 | Shm_unlink(shm_name); 318 | } 319 | exit(0); 320 | } 321 | 322 | int main(int argc, char* argv[]) { 323 | prog_name = "Judger"; 324 | if (argc != 3 && argc != 4 && argc != 5 && argc != 6) { 325 | usage(argv[0]); 326 | } 327 | 328 | // Parse the input 329 | player_path = argv[1]; 330 | map_file_path = argv[2]; 331 | if (argc >= 4) { 332 | constant_A = atoi(argv[3]); 333 | } else { 334 | constant_A = 8; 335 | } 336 | if (argc >= 5) { 337 | time_limit = atoi(argv[4]); 338 | if (time_limit <= 0) app_error("Bad value for `time_limit`"); 339 | } else { 340 | time_limit = INT_MAX; 341 | } 342 | if (argc >= 6) { 343 | game_server_path = argv[5]; 344 | } else { 345 | game_server_path = (char*)"./game_server"; 346 | } 347 | 348 | // Make sure all the files exist, and is executable 349 | make_sure_file_exists(player_path, "player's program"); 350 | make_sure_file_exists(map_file_path, "the map"); 351 | make_sure_file_exists(game_server_path, "the game server"); 352 | make_sure_file_is_executable(player_path, "player's program"); 353 | make_sure_file_is_executable(game_server_path, "the game server"); 354 | 355 | // Create pipes 356 | // 命名规则:fd_A_to_B 代表这个 fd 归 A 所有,这个 fd 所对应的 PIPE 的另一端归程序 B 所有 357 | // 连接方式详见本程序 (judger.cpp) 开头的注释 358 | // We use `create_pipe` instead of `Pipe` to make sure that 359 | // the result fd is greater or equal to 100, since that 360 | // the player's program may want to use fds below 100. 361 | create_pipe(fd_gs_from_pl, fd_pl_to_gs); 362 | create_pipe(fd_pl_from_gs, fd_gs_to_pl); 363 | create_pipe(fd_gs_from_ju, fd_ju_to_gs); 364 | create_pipe(fd_ju_from_gs, fd_gs_to_ju); 365 | 366 | // Create the shared memory region (used for communication between 367 | // the user's program and the game server) 368 | create_shared_memory_region(); 369 | 370 | // Set up the signal handlers 371 | Signal(SIGCHLD, sigchld_handler); 372 | Signal(SIGPIPE, sigpipe_handler); 373 | Signal(SIGALRM, sigalrm_handler); 374 | Signal(SIGINT, sigint_handler); 375 | 376 | // Launch the game server and the player's program 377 | // We block all signals here, in order to prevent SIGCHLD from disturbing us. 378 | block_all_signals(); 379 | create_game_server(); 380 | create_player(); 381 | Usleep(10000); // Sleep for a short time, increase stability 382 | Alarm(time_limit); // Set up the alarm. When time is up, we should receive a SIGALRM signal 383 | restore_prev_blockset(); 384 | 385 | // // Go to bed to sleep, and use sigsuspend() to wait for any signals 386 | // sigset_t cur_set; 387 | // Sigprocmask(SIG_SETMASK, NULL, &cur_set); // grab the sigset mask 388 | // while (true) { 389 | // Sigsuspend(&cur_set); 390 | // } 391 | 392 | // Go to bed to sleep. Wake up when signal arrives, or the game server 393 | // sends something to the judger (through fd_ju_from_gs) 394 | fd_set monitor_fd_set; 395 | while (true) { 396 | FD_ZERO(&monitor_fd_set); 397 | FD_SET(fd_ju_from_gs, &monitor_fd_set); 398 | Select(fd_ju_from_gs+1, &monitor_fd_set, NULL, NULL, NULL); 399 | // We received something from the game server 400 | // This happens when the player's program does something bad (e.g. requesting 401 | // too much channels; sends an invalid `click` response...) 402 | char buf[1024]; 403 | Read(fd_ju_from_gs, buf, 1024); 404 | log("The game server sends this to judger: "); 405 | fprintf(stderr, "\"%s\"\n", buf); 406 | log("So the judger will count the score and exit immediately.\n"); 407 | read_result_from_game_server_and_report(); 408 | } 409 | 410 | // The control flow should not reach here 411 | log("Error! The control flow reaches to the end of `main` in the judger.\n"); 412 | log(this_is_a_bug_str); 413 | return 0; 414 | } -------------------------------------------------------------------------------- /game_server.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | game_server - The minesweeper game server 3 | 4 | Notice: This program is designed to be launched by the judger(judger.cpp). 5 | Please do not launch it directly. 6 | 7 | It accepts the following envariables: MINESWEEPER_LAUNCHED_BY_JUDGER (must present), 8 | MINESWEEPER_MAP_FILE_PATH, MINESWEEPER_FD_GS_TO_PL, MINESWEEPER_FD_GS_FROM_PL, 9 | MINESWEEPER_FD_GS_TO_JU, MINESWEEPER_FD_GS_FROM_JU. 10 | It first parses those envariables and reads the map from the file 11 | indicated by MINESWEEPER_MAP_FILE_PATH. Then it begins to interact with 12 | the player's program. 13 | When the player's program sents an 'C' (stands for "Create Channel"), 14 | the game server creates a new channel and responses with the channel ID. 15 | When the player's program exits or the time is up, the judger sents an 'F' 16 | (stands for "Finished") character to the game server (received through 17 | `fd_from_ju`), and then the game server will send the number of opened 18 | non-mine grids and opened is-mine grids to the judger (sent through 19 | `fd_to_ju`) 20 | 21 | Overall Design: 22 | The main thread is responsible for listening to `fd_from_ju` and `fd_from_pl` 23 | at the same time (by I/O multiplexing). When the judger sents an 'F', the 24 | game server responses with the result (see above). When the player's program 25 | sents an 'C', it creates a new thread and let that thread handle that Channel. 26 | Each channel has a unique shared memory region. The player's program 27 | uses that region to communicate with the game server. (Note. we use shared 28 | memory instead of pipe/FIFO/message-queue to avoid overhead introduced by 29 | system calls). 30 | Each channel is handled by a particular thread, which we called "worker 31 | thread". The worker thread monitors the channel (namely, monitors the shared 32 | memory region). The worker thread acts like a "two-phase lock". After creation 33 | or completing a `click` request, the worker thread first spins for a certain 34 | times. If it did not detect further requests, it calls `futex_wait` and 35 | begins to sleep, until a new request arrives. We use this mechanism to ensure: 36 | - If the channel is busy (i.e. the player's program uses the channel 37 | frequently), then the worker thread just spins in the interval between 38 | two requests, achieving a better latency. 39 | - If the channel is not busy (i.e. the player's program only uses the 40 | channel from time to time), then if we still use a spin lock, we would 41 | waste a lot of CPU cycles (wasteful!). So we just use `futex_wait`, and 42 | let the kernel wake the worker thread up when the channel is active 43 | again, which doesn't lose much performance. 44 | 45 | Memory layout of a channel: 46 | Each channel has a shared memory (shm) region of `CHANNEL_SHM_SIZE` bytes, 47 | where `CHANNEL_SHM_SIZE` is defined in `common.h`. 48 | Memory layout: 49 | - 4 byte: `pending bit`. When the player's program wants to click, it sets this 50 | bit to 1 first. 51 | - 4 byte: `sleeping bit`. When the game server is about to enter the second phase 52 | (futex_wait), it sets this bit to 1. When the player's program wants to click, 53 | it tests whether this bit is 1. If it is 1 then the player's program will 54 | call `futex_wake()`. 55 | - 4 byte: `done bit`. When the game server completes the request, it 56 | sets this bit to 1. 57 | - 4 bytes `skip_when_reopen bit`. If it is 1 and the target grid of the 58 | current request has been opened before, then the game server will 59 | put -2 (or -3, if the grid contains a mine) in "bytes indicating 60 | how many grids are opened" and returns immediately. 61 | - 4 bytes `do_not_expand_bit`. If it is 1, then we only open the target 62 | grid, without "open the adjacent grids if the clicked grid contains a 63 | '0'". In other words, "间接点开" will not be proceed. 64 | - 2 bytes for click_r 65 | - 2 bytes for click_c 66 | - 4 bytes indicating how many grids are opened (-1 if the grid contains a mine, 67 | -2 if `re_report bit` is 0 and the target grid of the current request 68 | has been opened before) 69 | - 2 bytes r1, 2 bytes c1, 2 bytes number in grid (r1, c1) 70 | - 2 bytes r2, 2 bytes c2, 2 bytes number in grid (r2, c2) 71 | - ... 72 | - 2 bytes rK, 2 bytes cK, 2 bytes number in grid (rK, cK) 73 | */ 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include "lib/wrappers.h" 80 | #include "lib/log.h" 81 | #include "lib/common.h" 82 | #include "lib/shm.h" 83 | #include "lib/futex.h" 84 | #include "lib/queue.h" 85 | using std::atomic_flag, std::atomic, std::atomic_compare_exchange_strong; 86 | using std::pair, std::vector; 87 | using std::max, std::min; 88 | 89 | void kill_worker_threads(); 90 | void report_error_to_judger(const char* error_s); 91 | 92 | // The upper limit for active (doing BFS) worker threads 93 | // We need this because that every worker thread which is doing BFS needs a 94 | // vis[] array. If we allocate a unique vis[] array for each thread, then 95 | // the memory usage would be unacceptable. Instead, we just allocate a vis[] 96 | // array with NUM_ACTIVE_WORKER_THREAD*N*N bits, and when a thread wants 97 | // to BFS, it just use N*N bits from the array. 98 | constexpr int NUM_ACTIVE_WORKER_THREAD = 8; 99 | 100 | // The number of thread for `summarize()` 101 | constexpr int NUM_SUMMARIZE_THREAD = 8; 102 | 103 | // The spin amount in the two phase lock. 104 | constexpr int TWO_PHASE_LOCK_SPIN_AMUONT = 2048; 105 | 106 | long N, K, logN; // The size of the map, the number of mines 107 | char* map_file_path; // path to the map file 108 | int fd_to_pl, fd_from_pl; // fds (used to communicate with player's program) 109 | int fd_to_ju, fd_from_ju; // fds (used to communicate with the judger) 110 | 111 | char* shm_name; 112 | char* shm_start; // Point to the head of the shared memory region 113 | 114 | char* is_mine; // A large bit array, representing the map. 115 | inline char test_is_mine(long r, long c) { 116 | if (r < 0 || c < 0 || r >= N || c >= N) return 0; 117 | long index = (r<>offset&0x1; 120 | } 121 | inline char get_adj_mine(long r, long c) { 122 | return test_is_mine(r-1, c-1) + test_is_mine(r-1, c) + test_is_mine(r-1, c+1) 123 | + test_is_mine(r, c-1) + test_is_mine(r, c+1) 124 | + test_is_mine(r+1, c-1) + test_is_mine(r+1, c) + test_is_mine(r+1, c+1); 125 | } 126 | 127 | char* is_open; // A large bit array, representing whether the grid is opened by the player 128 | inline char test_is_open(long r, long c) { 129 | if (r < 0 || c < 0 || r >= N || c >= N) return 0; 130 | long index = (r<>offset&0x1; 133 | } 134 | inline void set_is_open(long r, long c) { 135 | long index = (r<* result = (pair*)Malloc(sizeof(pair)); 209 | result->first = cnt_non_mine; 210 | result->second = cnt_is_mine; 211 | return result; 212 | } 213 | 214 | // summarize - Send the number of opened non-mine grids and opened is-mine 215 | // grids to the judger, through fd_to_ju 216 | void summarize() { 217 | kill_worker_threads(); 218 | if (N%NUM_SUMMARIZE_THREAD != 0) { 219 | app_error("NUM_SUMMARIZE_THREAD must be a factor of N."); 220 | } 221 | // Create the threads for counting 222 | pthread_t tids[NUM_SUMMARIZE_THREAD]; 223 | for (int i = 0; i < NUM_SUMMARIZE_THREAD; ++i) { 224 | Pthread_create(tids+i, NULL, summarize_thread_routine, (void*)(long)i); 225 | } 226 | // Join those threads 227 | long cnt_non_mine = 0; 228 | long cnt_is_mine = 0; 229 | for (int i = 0; i < NUM_SUMMARIZE_THREAD; ++i) { 230 | pair* result; 231 | Pthread_join(tids[i], (void**)&result); 232 | cnt_non_mine += result->first; 233 | cnt_is_mine += result->second; 234 | } 235 | // Send it to the judger, via fd_to_ju 236 | // Format: "Status N K cnt_non_mine cnt_is_mine" 237 | char buf[128]; 238 | sprintf(buf, "%d %ld %ld %ld %ld", 239 | 0, N, K, cnt_non_mine, cnt_is_mine); 240 | Write(fd_to_ju, buf, strlen(buf)+1); 241 | // Clean up and exit 242 | Free(is_mine); 243 | Free(is_open); 244 | exit(0); 245 | } 246 | 247 | /* 248 | * Functions and variables for worker threads 249 | */ 250 | 251 | // A vector for maintaining all the tids of worker threads. 252 | pthread_mutex_t worker_thread_tids_mutex = PTHREAD_MUTEX_INITIALIZER; 253 | vector worker_thread_tids; 254 | 255 | // The channel_id of the next channel, starting from 0 256 | atomic next_channel_id = 0; 257 | 258 | // For BFS. Mark whether a grid is visited 259 | // We call `vis[i]` as "The i-th level" 260 | char* vis[NUM_ACTIVE_WORKER_THREAD]; 261 | char test_is_vis(int level, long r, long c) { 262 | if (r < 0 || c < 0 || r >= N || c >= N) return 1; 263 | long index = (r<>offset&0x1; 266 | } 267 | void set_is_vis(int level, long r, long c) { 268 | long index = (r< append_to_result = [&](long r, long c) { 307 | result_arr[result_open_count][0] = r; 308 | result_arr[result_open_count][1] = c; 309 | result_arr[result_open_count][2] = get_adj_mine(r, c); 310 | result_open_count += 1; 311 | }; 312 | result_open_count = 0; 313 | q.clear(); 314 | q.push(click_r, click_c); 315 | set_is_vis(level, click_r, click_c); 316 | append_to_result(click_r, click_c); 317 | while (!q.empty()) { 318 | int r, c; 319 | q.pop(r, c); 320 | for (int k = 0; k < 8; ++k) { 321 | int new_r = r + delta_xy[k][0]; 322 | int new_c = c + delta_xy[k][1]; 323 | if (!test_is_vis(level, new_r, new_c) && !test_is_mine(new_r, new_c)) { 324 | append_to_result(new_r, new_c); 325 | set_is_vis(level, new_r, new_c); 326 | if (!get_adj_mine(new_r, new_c)) { 327 | q.push(new_r, new_c); 328 | } 329 | } 330 | } 331 | } 332 | // Clean up vis[level] 333 | for (int i = 0; i < result_open_count; ++i) { 334 | unset_is_vis_area(level, result_arr[i][0], result_arr[i][1]); 335 | } 336 | // Open those grids 337 | for (int i = 0; i < result_open_count; ++i) { 338 | set_is_open(result_arr[i][0], result_arr[i][1]); 339 | } 340 | } 341 | 342 | // worker_thread_routine - Thread routine for a worker thread 343 | void* worker_thread_routine(void* arg) { 344 | Pthread_mutex_lock(&worker_thread_tids_mutex); 345 | // Make the thread cancellable 346 | Pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); 347 | Pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); 348 | worker_thread_tids.push_back(Pthread_self()); 349 | Pthread_mutex_unlock(&worker_thread_tids_mutex); 350 | 351 | // Create a new channel 352 | int channel_id = next_channel_id.fetch_add(1); 353 | if (channel_id >= MAX_CHANNEL) { 354 | char buf[128]; 355 | sprintf(buf, "Error! The player's program has opened too many channels. Limit: %d", MAX_CHANNEL); 356 | report_error_to_judger(buf); 357 | kill_worker_threads(); 358 | exit(0); 359 | return NULL; 360 | } 361 | char* shm_pos = shm_start + CHANNEL_SHM_SIZE*channel_id; 362 | init_shm_region(shm_pos); 363 | 364 | // Response to player's program with the channel ID (through fd_to_pl) 365 | char buf[16]; 366 | sprintf(buf, "%d", channel_id); 367 | Write(fd_to_pl, buf, strlen(buf)+1); 368 | 369 | // printf("is_mine %d\n", test_is_mine(1, 1)); 370 | // Go to 996! 371 | while (true) { 372 | // The two phase lock 373 | // First we spin for a while 374 | bool flag = false; 375 | for (int i = 0; i < TWO_PHASE_LOCK_SPIN_AMUONT; ++i) { 376 | if (SHM_PENDING_BIT(shm_pos)) { 377 | flag = true; 378 | break; 379 | } 380 | } 381 | // If we still cannot grab the lock, we use `futex` 382 | if (!flag) { 383 | SHM_SLEEPING_BIT(shm_pos) = 1; 384 | while (true) { 385 | if (SHM_PENDING_BIT(shm_pos) == 1) { 386 | break; 387 | } 388 | futex_wait(SHM_PENDING_BIT_PTR(shm_pos), 0); 389 | } 390 | } 391 | // I'm wake up 392 | // Cleanup 393 | SHM_PENDING_BIT(shm_pos) = 0; 394 | SHM_SLEEPING_BIT(shm_pos) = 0; 395 | // There is a new `click()` request 396 | long click_r = SHM_CLICK_R(shm_pos); 397 | long click_c = SHM_CLICK_C(shm_pos); 398 | bool skip_when_reopen = SHM_SKIP_WHEN_REOPEN_BIT(shm_pos); 399 | bool do_not_expand = SHM_DO_NOT_EXPAND_BIT(shm_pos); 400 | 401 | if (do_not_expand) { 402 | set_is_open(click_r, click_c); 403 | if (test_is_mine(click_r, click_c)) { 404 | SHM_OPENED_GRID_COUNT(shm_pos) = -1; 405 | } else { 406 | SHM_OPENED_GRID_COUNT(shm_pos) = 1; 407 | (*SHM_OPENED_GRID_ARR(shm_pos))[0][0] = click_r; 408 | (*SHM_OPENED_GRID_ARR(shm_pos))[0][1] = click_c; 409 | (*SHM_OPENED_GRID_ARR(shm_pos))[0][2] = get_adj_mine(click_r, click_c); 410 | } 411 | } else if (skip_when_reopen && test_is_open(click_r, click_c)) { 412 | // This grid has been opened before, and the player's program says 413 | // that "If the grid has been opened before, plz skip it" 414 | // So we just put -2 (non-mine) or -3 (is-mine) into SHM_OPEN_GRID_COUNT and return 415 | SHM_OPENED_GRID_COUNT(shm_pos) = test_is_mine(click_r, click_c) ? -3 : -2; 416 | } else { 417 | if (test_is_mine(click_r, click_c)) { 418 | // This grid contains a mine, BOOM SHAKALAKA! 419 | set_is_open(click_r, click_c); 420 | SHM_OPENED_GRID_COUNT(shm_pos) = -1; 421 | } else if (get_adj_mine(click_r, click_c)) { 422 | // This grid contains a non-zero number 423 | set_is_open(click_r, click_c); 424 | SHM_OPENED_GRID_COUNT(shm_pos) = 1; 425 | (*SHM_OPENED_GRID_ARR(shm_pos))[0][0] = click_r; 426 | (*SHM_OPENED_GRID_ARR(shm_pos))[0][1] = click_c; 427 | (*SHM_OPENED_GRID_ARR(shm_pos))[0][2] = get_adj_mine(click_r, click_c); 428 | } else { 429 | // This grid contains zero 430 | // BFS is needed 431 | int level = find_available_level(); 432 | long result_open_count = 0; 433 | worker_thread_bfs( 434 | click_r, click_c, level, result_open_count, 435 | *SHM_OPENED_GRID_ARR(shm_pos)); 436 | SHM_OPENED_GRID_COUNT(shm_pos) = result_open_count; 437 | release_level(level); 438 | } 439 | } 440 | 441 | // Done 442 | SHM_DONE_BIT(shm_pos) = 1; 443 | } 444 | 445 | return NULL; 446 | } 447 | 448 | 449 | /* 450 | * Functions and variables for the main thread 451 | */ 452 | 453 | // Kill all worker threads and report some error to the judger 454 | void kill_worker_threads() { 455 | Pthread_mutex_lock(&worker_thread_tids_mutex); 456 | for (pthread_t tid : worker_thread_tids) { 457 | Pthread_cancel(tid); 458 | } 459 | Pthread_mutex_unlock(&worker_thread_tids_mutex); 460 | } 461 | 462 | // Send something to the judger 463 | void report_error_to_judger(const char* error_s) { 464 | Pthread_mutex_lock(&worker_thread_tids_mutex); 465 | Write(fd_to_ju, error_s, strlen(error_s)+1); 466 | Pthread_mutex_unlock(&worker_thread_tids_mutex); 467 | } 468 | 469 | // main_thread_routine - Thread routine for the main thread. 470 | // (Actually this function is not a "thread routine" because it is not used 471 | // as an argument for `pthread_create`) 472 | void main_thread_routine() { 473 | static constexpr int BUF_LEN = 16; 474 | char buf[BUF_LEN]; 475 | fd_set monitor_fd_set; // fd set for I/O multiplexing, including fd_from_pl and fd_from_ju 476 | bool fd_from_pl_eofed = false; // Whether the player has closed the connection 477 | while (true) { 478 | FD_ZERO(&monitor_fd_set); 479 | if (!fd_from_pl_eofed) 480 | FD_SET(fd_from_pl, &monitor_fd_set); 481 | FD_SET(fd_from_ju, &monitor_fd_set); 482 | Select(max(fd_from_ju, fd_from_pl)+1, &monitor_fd_set, NULL, NULL, NULL); 483 | if (FD_ISSET(fd_from_pl, &monitor_fd_set)) { 484 | // Received something from the player's program 485 | int t = Read(fd_from_pl, buf, BUF_LEN); 486 | if (t == 0) { 487 | fd_from_pl_eofed = true; 488 | continue; 489 | } 490 | switch (buf[0]) { 491 | case 'C': 492 | // "I want to create a new channel" 493 | pthread_t tid; 494 | Pthread_create(&tid, NULL, worker_thread_routine, NULL); 495 | break; 496 | default: 497 | log("Error! Received something unknown from the player's program: %c (ASCII=%d)\n", buf[0], int(buf[0])); 498 | log(this_is_a_bug_str); 499 | exit(1); 500 | } 501 | } else if (FD_ISSET(fd_from_ju, &monitor_fd_set)) { 502 | // Received something from the judger 503 | Read(fd_from_ju, buf, BUF_LEN); 504 | switch (buf[0]) { 505 | case 'F': 506 | // "Finish judging, please report the result to the judger" 507 | summarize(); 508 | break; 509 | default: 510 | log("Error! Received something unknown from the judger: %c (ASCII=%d)\n", buf[0], int(buf[0])); 511 | log(this_is_a_bug_str); 512 | exit(1); 513 | } 514 | } else { 515 | // Received something from ... em ... ok maybe it is caused by the solar storm? 516 | log("Error! At line %d in `game_server`, received something from a unknown location\n", __LINE__); 517 | log(this_is_a_bug_str); 518 | exit(1); 519 | } 520 | } 521 | } 522 | 523 | int main(int argc, char* argv[]) { 524 | prog_name = "Game Server"; 525 | exit_when_parent_dies(); 526 | 527 | read_env_vars(); 528 | 529 | read_map(); 530 | 531 | // Alloc space for `is_open` 532 | is_open = (char*)Calloc(N*N/8, 1); 533 | // Alloc space for `vis` 534 | for (int i = 0; i < NUM_ACTIVE_WORKER_THREAD; ++i) { 535 | vis[i] = (char*)Calloc(N*N/8, 1); 536 | } 537 | // Initialize vis_occupied_flags 538 | for (int i = 0; i < NUM_ACTIVE_WORKER_THREAD; ++i) { 539 | vis_occupied_flags[i].clear(); 540 | } 541 | 542 | shm_start = open_shm(shm_name); 543 | 544 | // Send N and K to the players program, via `fd_to_pl` 545 | char buf[64]; 546 | sprintf(buf, "%ld %ld", N, K); 547 | Write(fd_to_pl, buf, strlen(buf)+1); 548 | 549 | main_thread_routine(); 550 | 551 | // The control flow should not reach here 552 | log("Error! The control flow reaches to the end of `main` in the game server.\n"); 553 | log(this_is_a_bug_str); 554 | return 1; 555 | } -------------------------------------------------------------------------------- /lib/csapp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * csapp.c - Functions for the CS:APP3e book 3 | * 4 | * Updated 10/2016 reb: 5 | * - Fixed bug in sio_ltoa that didn't cover negative numbers 6 | * 7 | * Updated 2/2016 droh: 8 | * - Updated open_clientfd and open_listenfd to fail more gracefully 9 | * 10 | * Updated 8/2014 droh: 11 | * - New versions of open_clientfd and open_listenfd are reentrant and 12 | * protocol independent. 13 | * 14 | * - Added protocol-independent inet_ntop and inet_pton functions. The 15 | * inet_ntoa and inet_aton functions are obsolete. 16 | * 17 | * Updated 7/2014 droh: 18 | * - Aded reentrant sio (signal-safe I/O) routines 19 | * 20 | * Updated 4/2013 droh: 21 | * - rio_readlineb: fixed edge case bug 22 | * - rio_readnb: removed redundant EINTR check 23 | */ 24 | /* $begin csapp.c */ 25 | #include "csapp.h" 26 | 27 | /************************** 28 | * Error-handling functions 29 | **************************/ 30 | const char* prog_name = NULL; 31 | /* $begin errorfuns */ 32 | /* $begin unixerror */ 33 | void unix_error(const char *msg) /* Unix-style error */ 34 | { 35 | if (prog_name) fprintf(stderr, "[%s] ", prog_name); 36 | fprintf(stderr, "%s: %s\n", msg, strerror(errno)); 37 | exit(0); 38 | } 39 | /* $end unixerror */ 40 | 41 | void posix_error(int code, const char *msg) /* Posix-style error */ 42 | { 43 | if (prog_name) fprintf(stderr, "[%s] ", prog_name); 44 | fprintf(stderr, "%s: %s\n", msg, strerror(code)); 45 | exit(0); 46 | } 47 | 48 | void getaddrinfo_error(int code, const char *msg) /* Getaddrinfo-style error */ 49 | { 50 | if (prog_name) fprintf(stderr, "[%s] ", prog_name); 51 | fprintf(stderr, "%s: %s\n", msg, gai_strerror(code)); 52 | exit(0); 53 | } 54 | 55 | void app_error(const char *format, ...) /* Application error */ 56 | { 57 | if (prog_name) fprintf(stderr, "[%s] ", prog_name); 58 | va_list arglist; 59 | va_start(arglist, format); 60 | vfprintf(stderr, format, arglist); 61 | va_end(arglist); 62 | exit(0); 63 | } 64 | /* $end errorfuns */ 65 | 66 | void dns_error(const char *msg) /* Obsolete gethostbyname error */ 67 | { 68 | fprintf(stderr, "%s\n", msg); 69 | exit(0); 70 | } 71 | 72 | 73 | /********************************************* 74 | * Wrappers for Unix process control functions 75 | ********************************************/ 76 | 77 | /* $begin forkwrapper */ 78 | pid_t Fork(void) 79 | { 80 | pid_t pid; 81 | 82 | if ((pid = fork()) < 0) 83 | unix_error("Fork error"); 84 | return pid; 85 | } 86 | /* $end forkwrapper */ 87 | 88 | void Execve(const char *filename, char *const argv[], char *const envp[]) 89 | { 90 | if (execve(filename, argv, envp) < 0) 91 | unix_error("Execve error"); 92 | } 93 | 94 | /* $begin wait */ 95 | pid_t Wait(int *status) 96 | { 97 | pid_t pid; 98 | 99 | if ((pid = wait(status)) < 0) 100 | unix_error("Wait error"); 101 | return pid; 102 | } 103 | /* $end wait */ 104 | 105 | pid_t Waitpid(pid_t pid, int *iptr, int options) 106 | { 107 | pid_t retpid; 108 | 109 | if ((retpid = waitpid(pid, iptr, options)) < 0) 110 | unix_error("Waitpid error"); 111 | return(retpid); 112 | } 113 | 114 | /* $begin kill */ 115 | void Kill(pid_t pid, int signum) 116 | { 117 | int rc; 118 | 119 | if ((rc = kill(pid, signum)) < 0) 120 | unix_error("Kill error"); 121 | } 122 | /* $end kill */ 123 | 124 | void Pause() 125 | { 126 | (void)pause(); 127 | return; 128 | } 129 | 130 | unsigned int Sleep(unsigned int secs) 131 | { 132 | unsigned int rc; 133 | 134 | if ((rc = sleep(secs)) < 0) 135 | unix_error("Sleep error"); 136 | return rc; 137 | } 138 | 139 | unsigned int Alarm(unsigned int seconds) { 140 | return alarm(seconds); 141 | } 142 | 143 | void Setpgid(pid_t pid, pid_t pgid) { 144 | int rc; 145 | 146 | if ((rc = setpgid(pid, pgid)) < 0) 147 | unix_error("Setpgid error"); 148 | return; 149 | } 150 | 151 | pid_t Getpgrp(void) { 152 | return getpgrp(); 153 | } 154 | 155 | /************************************ 156 | * Wrappers for Unix signal functions 157 | ***********************************/ 158 | 159 | /* $begin sigaction */ 160 | handler_t *Signal(int signum, handler_t *handler) 161 | { 162 | struct sigaction action, old_action; 163 | 164 | action.sa_handler = handler; 165 | sigemptyset(&action.sa_mask); /* Block sigs of type being handled */ 166 | action.sa_flags = SA_RESTART; /* Restart syscalls if possible */ 167 | 168 | if (sigaction(signum, &action, &old_action) < 0) 169 | unix_error("Signal error"); 170 | return (old_action.sa_handler); 171 | } 172 | /* $end sigaction */ 173 | 174 | void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset) 175 | { 176 | if (sigprocmask(how, set, oldset) < 0) 177 | unix_error("Sigprocmask error"); 178 | return; 179 | } 180 | 181 | void Sigemptyset(sigset_t *set) 182 | { 183 | if (sigemptyset(set) < 0) 184 | unix_error("Sigemptyset error"); 185 | return; 186 | } 187 | 188 | void Sigfillset(sigset_t *set) 189 | { 190 | if (sigfillset(set) < 0) 191 | unix_error("Sigfillset error"); 192 | return; 193 | } 194 | 195 | void Sigaddset(sigset_t *set, int signum) 196 | { 197 | if (sigaddset(set, signum) < 0) 198 | unix_error("Sigaddset error"); 199 | return; 200 | } 201 | 202 | void Sigdelset(sigset_t *set, int signum) 203 | { 204 | if (sigdelset(set, signum) < 0) 205 | unix_error("Sigdelset error"); 206 | return; 207 | } 208 | 209 | int Sigismember(const sigset_t *set, int signum) 210 | { 211 | int rc; 212 | if ((rc = sigismember(set, signum)) < 0) 213 | unix_error("Sigismember error"); 214 | return rc; 215 | } 216 | 217 | int Sigsuspend(const sigset_t *set) 218 | { 219 | int rc = sigsuspend(set); /* always returns -1 */ 220 | if (errno != EINTR) 221 | unix_error("Sigsuspend error"); 222 | return rc; 223 | } 224 | 225 | /************************************************************* 226 | * The Sio (Signal-safe I/O) package - simple reentrant output 227 | * functions that are safe for signal handlers. 228 | *************************************************************/ 229 | 230 | /* Private sio functions */ 231 | 232 | /* $begin sioprivate */ 233 | /* sio_reverse - Reverse a string (from K&R) */ 234 | static void sio_reverse(char s[]) 235 | { 236 | int c, i, j; 237 | 238 | for (i = 0, j = strlen(s)-1; i < j; i++, j--) { 239 | c = s[i]; 240 | s[i] = s[j]; 241 | s[j] = c; 242 | } 243 | } 244 | 245 | /* sio_ltoa - Convert long to base b string (from K&R) */ 246 | void sio_ltoa(long v, char s[], int b) 247 | { 248 | int c, i = 0; 249 | int neg = v < 0; 250 | 251 | if (neg) 252 | v = -v; 253 | 254 | do { 255 | s[i++] = ((c = (v % b)) < 10) ? c + '0' : c - 10 + 'a'; 256 | } while ((v /= b) > 0); 257 | 258 | if (neg) 259 | s[i++] = '-'; 260 | 261 | s[i] = '\0'; 262 | sio_reverse(s); 263 | } 264 | 265 | /* sio_strlen - Return length of string (from K&R) */ 266 | size_t sio_strlen(const char s[]) 267 | { 268 | int i = 0; 269 | 270 | while (s[i] != '\0') 271 | ++i; 272 | return i; 273 | } 274 | /* $end sioprivate */ 275 | 276 | /* Public Sio functions */ 277 | /* $begin siopublic */ 278 | 279 | void sio_put(const char s[]) /* Put string */ 280 | { 281 | Write(STDERR_FILENO, s, sio_strlen(s)); //line:csapp:siostrlen 282 | } 283 | 284 | void sio_put(long v) /* Put long */ 285 | { 286 | char s[128]; 287 | 288 | sio_ltoa(v, s, 10); /* Based on K&R itoa() */ //line:csapp:sioltoa 289 | sio_put(s); 290 | } 291 | 292 | void sio_error(const char s[]) /* Put error message and exit */ 293 | { 294 | sio_put(s); 295 | _exit(1); //line:csapp:sioexit 296 | } 297 | /* $end siopublic */ 298 | 299 | /******************************** 300 | * Wrappers for Unix I/O routines 301 | ********************************/ 302 | 303 | int Open(const char *pathname, int flags, mode_t mode) 304 | { 305 | int rc; 306 | 307 | if ((rc = open(pathname, flags, mode)) < 0) 308 | unix_error("Open error"); 309 | return rc; 310 | } 311 | 312 | ssize_t Read(int fd, void *buf, size_t count) 313 | { 314 | ssize_t rc; 315 | 316 | if ((rc = read(fd, buf, count)) < 0) 317 | unix_error("Read error"); 318 | return rc; 319 | } 320 | 321 | ssize_t Write(int fd, const void *buf, size_t count) 322 | { 323 | ssize_t rc; 324 | 325 | if ((rc = write(fd, buf, count)) < 0) 326 | unix_error("Write error"); 327 | return rc; 328 | } 329 | 330 | off_t Lseek(int fildes, off_t offset, int whence) 331 | { 332 | off_t rc; 333 | 334 | if ((rc = lseek(fildes, offset, whence)) < 0) 335 | unix_error("Lseek error"); 336 | return rc; 337 | } 338 | 339 | void Close(int fd) 340 | { 341 | int rc; 342 | 343 | if ((rc = close(fd)) < 0) 344 | unix_error("Close error"); 345 | } 346 | 347 | int Select(int n, fd_set *readfds, fd_set *writefds, 348 | fd_set *exceptfds, struct timeval *timeout) 349 | { 350 | int rc; 351 | 352 | if ((rc = select(n, readfds, writefds, exceptfds, timeout)) < 0) 353 | unix_error("Select error"); 354 | return rc; 355 | } 356 | 357 | int Dup2(int fd1, int fd2) 358 | { 359 | int rc; 360 | 361 | if ((rc = dup2(fd1, fd2)) < 0) 362 | unix_error("Dup2 error"); 363 | return rc; 364 | } 365 | 366 | void Stat(const char *filename, struct stat *buf) 367 | { 368 | if (stat(filename, buf) < 0) 369 | unix_error("Stat error"); 370 | } 371 | 372 | void Fstat(int fd, struct stat *buf) 373 | { 374 | if (fstat(fd, buf) < 0) 375 | unix_error("Fstat error"); 376 | } 377 | 378 | /********************************* 379 | * Wrappers for directory function 380 | *********************************/ 381 | 382 | DIR *Opendir(const char *name) 383 | { 384 | DIR *dirp = opendir(name); 385 | 386 | if (!dirp) 387 | unix_error("opendir error"); 388 | return dirp; 389 | } 390 | 391 | struct dirent *Readdir(DIR *dirp) 392 | { 393 | struct dirent *dep; 394 | 395 | errno = 0; 396 | dep = readdir(dirp); 397 | if ((dep == NULL) && (errno != 0)) 398 | unix_error("readdir error"); 399 | return dep; 400 | } 401 | 402 | int Closedir(DIR *dirp) 403 | { 404 | int rc; 405 | 406 | if ((rc = closedir(dirp)) < 0) 407 | unix_error("closedir error"); 408 | return rc; 409 | } 410 | 411 | /*************************************** 412 | * Wrappers for memory mapping functions 413 | ***************************************/ 414 | void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset) 415 | { 416 | void *ptr; 417 | 418 | if ((ptr = mmap(addr, len, prot, flags, fd, offset)) == ((void *) -1)) 419 | unix_error("mmap error"); 420 | return(ptr); 421 | } 422 | 423 | void Munmap(void *start, size_t length) 424 | { 425 | if (munmap(start, length) < 0) 426 | unix_error("munmap error"); 427 | } 428 | 429 | /*************************************************** 430 | * Wrappers for dynamic storage allocation functions 431 | ***************************************************/ 432 | 433 | void *Malloc(size_t size) 434 | { 435 | void *p; 436 | 437 | if ((p = malloc(size)) == NULL) 438 | unix_error("Malloc error"); 439 | return p; 440 | } 441 | 442 | void *Realloc(void *ptr, size_t size) 443 | { 444 | void *p; 445 | 446 | if ((p = realloc(ptr, size)) == NULL) 447 | unix_error("Realloc error"); 448 | return p; 449 | } 450 | 451 | void *Calloc(size_t nmemb, size_t size) 452 | { 453 | void *p; 454 | 455 | if ((p = calloc(nmemb, size)) == NULL) 456 | unix_error("Calloc error"); 457 | return p; 458 | } 459 | 460 | void Free(void *ptr) 461 | { 462 | free(ptr); 463 | } 464 | 465 | /****************************************** 466 | * Wrappers for the Standard I/O functions. 467 | ******************************************/ 468 | void Fclose(FILE *fp) 469 | { 470 | if (fclose(fp) != 0) 471 | unix_error("Fclose error"); 472 | } 473 | 474 | FILE *Fdopen(int fd, const char *type) 475 | { 476 | FILE *fp; 477 | 478 | if ((fp = fdopen(fd, type)) == NULL) 479 | unix_error("Fdopen error"); 480 | 481 | return fp; 482 | } 483 | 484 | char *Fgets(char *ptr, int n, FILE *stream) 485 | { 486 | char *rptr; 487 | 488 | if (((rptr = fgets(ptr, n, stream)) == NULL) && ferror(stream)) 489 | app_error("Fgets error"); 490 | 491 | return rptr; 492 | } 493 | 494 | FILE *Fopen(const char *filename, const char *mode) 495 | { 496 | FILE *fp; 497 | 498 | if ((fp = fopen(filename, mode)) == NULL) 499 | unix_error("Fopen error"); 500 | 501 | return fp; 502 | } 503 | 504 | void Fputs(const char *ptr, FILE *stream) 505 | { 506 | if (fputs(ptr, stream) == EOF) 507 | unix_error("Fputs error"); 508 | } 509 | 510 | size_t Fread(void *ptr, size_t size, size_t nmemb, FILE *stream) 511 | { 512 | size_t n; 513 | 514 | if (((n = fread(ptr, size, nmemb, stream)) < nmemb) && ferror(stream)) 515 | unix_error("Fread error"); 516 | return n; 517 | } 518 | 519 | void Fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) 520 | { 521 | if (fwrite(ptr, size, nmemb, stream) < nmemb) 522 | unix_error("Fwrite error"); 523 | } 524 | 525 | 526 | /**************************** 527 | * Sockets interface wrappers 528 | ****************************/ 529 | 530 | int Socket(int domain, int type, int protocol) 531 | { 532 | int rc; 533 | 534 | if ((rc = socket(domain, type, protocol)) < 0) 535 | unix_error("Socket error"); 536 | return rc; 537 | } 538 | 539 | void Setsockopt(int s, int level, int optname, const void *optval, int optlen) 540 | { 541 | int rc; 542 | 543 | if ((rc = setsockopt(s, level, optname, optval, optlen)) < 0) 544 | unix_error("Setsockopt error"); 545 | } 546 | 547 | void Bind(int sockfd, struct sockaddr *my_addr, int addrlen) 548 | { 549 | int rc; 550 | 551 | if ((rc = bind(sockfd, my_addr, addrlen)) < 0) 552 | unix_error("Bind error"); 553 | } 554 | 555 | void Listen(int s, int backlog) 556 | { 557 | int rc; 558 | 559 | if ((rc = listen(s, backlog)) < 0) 560 | unix_error("Listen error"); 561 | } 562 | 563 | int Accept(int s, struct sockaddr *addr, socklen_t *addrlen) 564 | { 565 | int rc; 566 | 567 | if ((rc = accept(s, addr, addrlen)) < 0) 568 | unix_error("Accept error"); 569 | return rc; 570 | } 571 | 572 | void Connect(int sockfd, struct sockaddr *serv_addr, int addrlen) 573 | { 574 | int rc; 575 | 576 | if ((rc = connect(sockfd, serv_addr, addrlen)) < 0) 577 | unix_error("Connect error"); 578 | } 579 | 580 | /******************************* 581 | * Protocol-independent wrappers 582 | *******************************/ 583 | /* $begin getaddrinfo */ 584 | void Getaddrinfo(const char *node, const char *service, 585 | const struct addrinfo *hints, struct addrinfo **res) 586 | { 587 | int rc; 588 | 589 | if ((rc = getaddrinfo(node, service, hints, res)) != 0) 590 | getaddrinfo_error(rc, "Getaddrinfo error"); 591 | } 592 | /* $end getaddrinfo */ 593 | 594 | void Getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, 595 | size_t hostlen, char *serv, size_t servlen, int flags) 596 | { 597 | int rc; 598 | 599 | if ((rc = getnameinfo(sa, salen, host, hostlen, serv, 600 | servlen, flags)) != 0) 601 | getaddrinfo_error(rc, "Getnameinfo error"); 602 | } 603 | 604 | void Freeaddrinfo(struct addrinfo *res) 605 | { 606 | freeaddrinfo(res); 607 | } 608 | 609 | void Inet_ntop(int af, const void *src, char *dst, socklen_t size) 610 | { 611 | if (!inet_ntop(af, src, dst, size)) 612 | unix_error("Inet_ntop error"); 613 | } 614 | 615 | void Inet_pton(int af, const char *src, void *dst) 616 | { 617 | int rc; 618 | 619 | rc = inet_pton(af, src, dst); 620 | if (rc == 0) 621 | app_error("inet_pton error: invalid dotted-decimal address"); 622 | else if (rc < 0) 623 | unix_error("Inet_pton error"); 624 | } 625 | 626 | /******************************************* 627 | * DNS interface wrappers. 628 | * 629 | * NOTE: These are obsolete because they are not thread safe. Use 630 | * getaddrinfo and getnameinfo instead 631 | ***********************************/ 632 | 633 | /* $begin gethostbyname */ 634 | struct hostent *Gethostbyname(const char *name) 635 | { 636 | struct hostent *p; 637 | 638 | if ((p = gethostbyname(name)) == NULL) 639 | dns_error("Gethostbyname error"); 640 | return p; 641 | } 642 | /* $end gethostbyname */ 643 | 644 | struct hostent *Gethostbyaddr(const char *addr, int len, int type) 645 | { 646 | struct hostent *p; 647 | 648 | if ((p = gethostbyaddr(addr, len, type)) == NULL) 649 | dns_error("Gethostbyaddr error"); 650 | return p; 651 | } 652 | 653 | /************************************************ 654 | * Wrappers for Pthreads thread control functions 655 | ************************************************/ 656 | 657 | void Pthread_create(pthread_t *tidp, pthread_attr_t *attrp, 658 | void * (*routine)(void *), void *argp) 659 | { 660 | int rc; 661 | 662 | if ((rc = pthread_create(tidp, attrp, routine, argp)) != 0) 663 | posix_error(rc, "Pthread_create error"); 664 | } 665 | 666 | void Pthread_cancel(pthread_t tid) { 667 | int rc; 668 | 669 | if ((rc = pthread_cancel(tid)) != 0) 670 | posix_error(rc, "Pthread_cancel error"); 671 | } 672 | 673 | void Pthread_join(pthread_t tid, void **thread_return) { 674 | int rc; 675 | 676 | if ((rc = pthread_join(tid, thread_return)) != 0) 677 | posix_error(rc, "Pthread_join error"); 678 | } 679 | 680 | /* $begin detach */ 681 | void Pthread_detach(pthread_t tid) { 682 | int rc; 683 | 684 | if ((rc = pthread_detach(tid)) != 0) 685 | posix_error(rc, "Pthread_detach error"); 686 | } 687 | /* $end detach */ 688 | 689 | void Pthread_exit(void *retval) { 690 | pthread_exit(retval); 691 | } 692 | 693 | pthread_t Pthread_self(void) { 694 | return pthread_self(); 695 | } 696 | 697 | void Pthread_once(pthread_once_t *once_control, void (*init_function)()) { 698 | pthread_once(once_control, init_function); 699 | } 700 | 701 | void Pthread_mutex_lock(pthread_mutex_t *mutex) { 702 | int rc; 703 | if ((rc = pthread_mutex_lock(mutex)) != 0) 704 | posix_error(rc, "Pthread_mutex_lock error"); 705 | } 706 | 707 | void Pthread_mutex_unlock(pthread_mutex_t *mutex) { 708 | int rc; 709 | if ((rc = pthread_mutex_unlock(mutex)) != 0) 710 | posix_error(rc, "Pthread_mutex_unlock error"); 711 | } 712 | 713 | void Pthread_kill(pthread_t tid, int signal) { 714 | int rc; 715 | if ((rc = pthread_kill(tid, signal)) != 0) 716 | posix_error(rc, "Pthread_kill error"); 717 | } 718 | 719 | /******************************* 720 | * Wrappers for Posix semaphores 721 | *******************************/ 722 | 723 | void Sem_init(sem_t *sem, int pshared, unsigned int value) 724 | { 725 | if (sem_init(sem, pshared, value) < 0) 726 | unix_error("Sem_init error"); 727 | } 728 | 729 | void P(sem_t *sem) 730 | { 731 | if (sem_wait(sem) < 0) 732 | unix_error("P error"); 733 | } 734 | 735 | void V(sem_t *sem) 736 | { 737 | if (sem_post(sem) < 0) 738 | unix_error("V error"); 739 | } 740 | 741 | /**************************************** 742 | * The Rio package - Robust I/O functions 743 | ****************************************/ 744 | 745 | /* 746 | * rio_readn - Robustly read n bytes (unbuffered) 747 | */ 748 | /* $begin rio_readn */ 749 | ssize_t rio_readn(int fd, void *usrbuf, size_t n) 750 | { 751 | size_t nleft = n; 752 | ssize_t nread; 753 | char *bufp = (char*)usrbuf; 754 | 755 | while (nleft > 0) { 756 | if ((nread = read(fd, bufp, nleft)) < 0) { 757 | if (errno == EINTR) /* Interrupted by sig handler return */ 758 | nread = 0; /* and call read() again */ 759 | else 760 | return -1; /* errno set by read() */ 761 | } 762 | else if (nread == 0) 763 | break; /* EOF */ 764 | nleft -= nread; 765 | bufp += nread; 766 | } 767 | return (n - nleft); /* Return >= 0 */ 768 | } 769 | /* $end rio_readn */ 770 | 771 | /* 772 | * rio_writen - Robustly write n bytes (unbuffered) 773 | */ 774 | /* $begin rio_writen */ 775 | ssize_t rio_writen(int fd, void *usrbuf, size_t n) 776 | { 777 | size_t nleft = n; 778 | ssize_t nwritten; 779 | char *bufp = (char*)usrbuf; 780 | 781 | while (nleft > 0) { 782 | if ((nwritten = write(fd, bufp, nleft)) <= 0) { 783 | if (errno == EINTR) /* Interrupted by sig handler return */ 784 | nwritten = 0; /* and call write() again */ 785 | else 786 | return -1; /* errno set by write() */ 787 | } 788 | nleft -= nwritten; 789 | bufp += nwritten; 790 | } 791 | return n; 792 | } 793 | /* $end rio_writen */ 794 | 795 | 796 | /* 797 | * rio_read - This is a wrapper for the Unix read() function that 798 | * transfers min(n, rio_cnt) bytes from an internal buffer to a user 799 | * buffer, where n is the number of bytes requested by the user and 800 | * rio_cnt is the number of unread bytes in the internal buffer. On 801 | * entry, rio_read() refills the internal buffer via a call to 802 | * read() if the internal buffer is empty. 803 | */ 804 | /* $begin rio_read */ 805 | static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n) 806 | { 807 | int cnt; 808 | 809 | while (rp->rio_cnt <= 0) { /* Refill if buf is empty */ 810 | rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, 811 | sizeof(rp->rio_buf)); 812 | if (rp->rio_cnt < 0) { 813 | if (errno != EINTR) /* Interrupted by sig handler return */ 814 | return -1; 815 | } 816 | else if (rp->rio_cnt == 0) /* EOF */ 817 | return 0; 818 | else 819 | rp->rio_bufptr = rp->rio_buf; /* Reset buffer ptr */ 820 | } 821 | 822 | /* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */ 823 | cnt = n; 824 | if ((size_t)rp->rio_cnt < n) 825 | cnt = rp->rio_cnt; 826 | memcpy(usrbuf, rp->rio_bufptr, cnt); 827 | rp->rio_bufptr += cnt; 828 | rp->rio_cnt -= cnt; 829 | return cnt; 830 | } 831 | /* $end rio_read */ 832 | 833 | /* 834 | * rio_readinitb - Associate a descriptor with a read buffer and reset buffer 835 | */ 836 | /* $begin rio_readinitb */ 837 | void rio_readinitb(rio_t *rp, int fd) 838 | { 839 | rp->rio_fd = fd; 840 | rp->rio_cnt = 0; 841 | rp->rio_bufptr = rp->rio_buf; 842 | } 843 | /* $end rio_readinitb */ 844 | 845 | /* 846 | * rio_readnb - Robustly read n bytes (buffered) 847 | */ 848 | /* $begin rio_readnb */ 849 | ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n) 850 | { 851 | size_t nleft = n; 852 | ssize_t nread; 853 | char *bufp = (char*)usrbuf; 854 | 855 | while (nleft > 0) { 856 | if ((nread = rio_read(rp, bufp, nleft)) < 0) 857 | return -1; /* errno set by read() */ 858 | else if (nread == 0) 859 | break; /* EOF */ 860 | nleft -= nread; 861 | bufp += nread; 862 | } 863 | return (n - nleft); /* return >= 0 */ 864 | } 865 | /* $end rio_readnb */ 866 | 867 | /* 868 | * rio_readlineb - Robustly read a text line (buffered) 869 | */ 870 | /* $begin rio_readlineb */ 871 | ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) 872 | { 873 | int rc; 874 | size_t n; 875 | char c, *bufp = (char*)usrbuf; 876 | 877 | for (n = 1; n < maxlen; n++) { 878 | if ((rc = rio_read(rp, &c, 1)) == 1) { 879 | *bufp++ = c; 880 | if (c == '\n') { 881 | n++; 882 | break; 883 | } 884 | } else if (rc == 0) { 885 | if (n == 1) 886 | return 0; /* EOF, no data read */ 887 | else 888 | break; /* EOF, some data was read */ 889 | } else 890 | return -1; /* Error */ 891 | } 892 | *bufp = 0; 893 | return n-1; 894 | } 895 | /* $end rio_readlineb */ 896 | 897 | /********************************** 898 | * Wrappers for robust I/O routines 899 | **********************************/ 900 | ssize_t Rio_readn(int fd, void *ptr, size_t nbytes) 901 | { 902 | ssize_t n; 903 | 904 | if ((n = rio_readn(fd, ptr, nbytes)) < 0) 905 | unix_error("Rio_readn error"); 906 | return n; 907 | } 908 | 909 | void Rio_writen(int fd, void *usrbuf, size_t n) 910 | { 911 | if ((size_t)rio_writen(fd, usrbuf, n) != n) 912 | unix_error("Rio_writen error"); 913 | } 914 | 915 | void Rio_readinitb(rio_t *rp, int fd) 916 | { 917 | rio_readinitb(rp, fd); 918 | } 919 | 920 | ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n) 921 | { 922 | ssize_t rc; 923 | 924 | if ((rc = rio_readnb(rp, usrbuf, n)) < 0) 925 | unix_error("Rio_readnb error"); 926 | return rc; 927 | } 928 | 929 | ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) 930 | { 931 | ssize_t rc; 932 | 933 | if ((rc = rio_readlineb(rp, usrbuf, maxlen)) < 0) 934 | unix_error("Rio_readlineb error"); 935 | return rc; 936 | } 937 | 938 | /******************************** 939 | * Client/server helper functions 940 | ********************************/ 941 | /* 942 | * open_clientfd - Open connection to server at and 943 | * return a socket descriptor ready for reading and writing. This 944 | * function is reentrant and protocol-independent. 945 | * 946 | * On error, returns: 947 | * -2 for getaddrinfo error 948 | * -1 with errno set for other errors. 949 | */ 950 | /* $begin open_clientfd */ 951 | int open_clientfd(char *hostname, char *port) { 952 | int clientfd, rc; 953 | struct addrinfo hints, *listp, *p; 954 | 955 | /* Get a list of potential server addresses */ 956 | memset(&hints, 0, sizeof(struct addrinfo)); 957 | hints.ai_socktype = SOCK_STREAM; /* Open a connection */ 958 | hints.ai_flags = AI_NUMERICSERV; /* ... using a numeric port arg. */ 959 | hints.ai_flags |= AI_ADDRCONFIG; /* Recommended for connections */ 960 | if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) { 961 | fprintf(stderr, "getaddrinfo failed (%s:%s): %s\n", hostname, port, gai_strerror(rc)); 962 | return -2; 963 | } 964 | 965 | /* Walk the list for one that we can successfully connect to */ 966 | for (p = listp; p; p = p->ai_next) { 967 | /* Create a socket descriptor */ 968 | if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) 969 | continue; /* Socket failed, try the next */ 970 | 971 | /* Connect to the server */ 972 | if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) 973 | break; /* Success */ 974 | if (close(clientfd) < 0) { /* Connect failed, try another */ //line:netp:openclientfd:closefd 975 | fprintf(stderr, "open_clientfd: close failed: %s\n", strerror(errno)); 976 | return -1; 977 | } 978 | } 979 | 980 | /* Clean up */ 981 | freeaddrinfo(listp); 982 | if (!p) /* All connects failed */ 983 | return -1; 984 | else /* The last connect succeeded */ 985 | return clientfd; 986 | } 987 | /* $end open_clientfd */ 988 | 989 | /* 990 | * open_listenfd - Open and return a listening socket on port. This 991 | * function is reentrant and protocol-independent. 992 | * 993 | * On error, returns: 994 | * -2 for getaddrinfo error 995 | * -1 with errno set for other errors. 996 | */ 997 | /* $begin open_listenfd */ 998 | int open_listenfd(char *port) 999 | { 1000 | struct addrinfo hints, *listp, *p; 1001 | int listenfd, rc, optval=1; 1002 | 1003 | /* Get a list of potential server addresses */ 1004 | memset(&hints, 0, sizeof(struct addrinfo)); 1005 | hints.ai_socktype = SOCK_STREAM; /* Accept connections */ 1006 | hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... on any IP address */ 1007 | hints.ai_flags |= AI_NUMERICSERV; /* ... using port number */ 1008 | if ((rc = getaddrinfo(NULL, port, &hints, &listp)) != 0) { 1009 | fprintf(stderr, "getaddrinfo failed (port %s): %s\n", port, gai_strerror(rc)); 1010 | return -2; 1011 | } 1012 | 1013 | /* Walk the list for one that we can bind to */ 1014 | for (p = listp; p; p = p->ai_next) { 1015 | /* Create a socket descriptor */ 1016 | if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) 1017 | continue; /* Socket failed, try the next */ 1018 | 1019 | /* Eliminates "Address already in use" error from bind */ 1020 | setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, //line:netp:csapp:setsockopt 1021 | (const void *)&optval , sizeof(int)); 1022 | 1023 | /* Bind the descriptor to the address */ 1024 | if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) 1025 | break; /* Success */ 1026 | if (close(listenfd) < 0) { /* Bind failed, try the next */ 1027 | fprintf(stderr, "open_listenfd close failed: %s\n", strerror(errno)); 1028 | return -1; 1029 | } 1030 | } 1031 | 1032 | 1033 | /* Clean up */ 1034 | freeaddrinfo(listp); 1035 | if (!p) /* No address worked */ 1036 | return -1; 1037 | 1038 | /* Make it a listening socket ready to accept connection requests */ 1039 | if (listen(listenfd, LISTENQ) < 0) { 1040 | close(listenfd); 1041 | return -1; 1042 | } 1043 | return listenfd; 1044 | } 1045 | /* $end open_listenfd */ 1046 | 1047 | /**************************************************** 1048 | * Wrappers for reentrant protocol-independent helpers 1049 | ****************************************************/ 1050 | int Open_clientfd(char *hostname, char *port) 1051 | { 1052 | int rc; 1053 | 1054 | if ((rc = open_clientfd(hostname, port)) < 0) 1055 | unix_error("Open_clientfd error"); 1056 | return rc; 1057 | } 1058 | 1059 | int Open_listenfd(char *port) 1060 | { 1061 | int rc; 1062 | 1063 | if ((rc = open_listenfd(port)) < 0) 1064 | unix_error("Open_listenfd error"); 1065 | return rc; 1066 | } 1067 | 1068 | /* $end csapp.c */ 1069 | 1070 | 1071 | 1072 | --------------------------------------------------------------------------------