創新實訓(七)——比賽(Contest)內部邏輯處理

山城甘草發表於2024-06-22

比賽部分

比賽部分包含比賽列表顯示、單個比賽內部資訊顯示、比賽管理、比賽人員以及比賽報名頁面這五個程式碼主要程式碼檔案。此外在內部邏輯處理時還使用了modellib下的部分配置程式碼

比賽列表

比賽列表包含“正在進行或即將到來的比賽”和“已結束的比賽”這兩部分具體程式碼實現解析如下

生成單個比賽資訊

在php程式碼中定義echoContest,用於生成單行比賽的列表資訊。該方法將封裝並利用於生成長表格

// 生成單個比賽資訊的方法封裝
	function echoContest($contest) {
		global $myUser, $upcoming_contest_name, $upcoming_contest_href, $rest_second;
		
		// 比賽主要連結資訊的HTML程式碼生成
		$contest_name_link = <<<EOD
<a href="/contest/{$contest['id']}">{$contest['name']}</a>
EOD;
		genMoreContestInfo($contest);
		// 根據比賽狀態進行前端頁面顯示更改,新增contest_name_link的程式碼細節
		if ($contest['cur_progress'] == CONTEST_NOT_STARTED) {
			$cur_rest_second = $contest['start_time']->getTimestamp() - UOJTime::$time_now->getTimestamp();
			if ($cur_rest_second < $rest_second) {
				$upcoming_contest_name = $contest['name'];
				$upcoming_contest_href = "/contest/{$contest['id']}";
				$rest_second = $cur_rest_second;
			}
			if ($myUser != null && hasRegistered($myUser, $contest)) {
				$contest_name_link .= '<sup><a style="color:green">'.UOJLocale::get('contests::registered').'</a></sup>';
			} else {
				$contest_name_link .= '<sup><a style="color:red" href="/contest/'.$contest['id'].'/register">'.UOJLocale::get('contests::register').'</a></sup>';
			}
		} elseif ($contest['cur_progress'] == CONTEST_IN_PROGRESS) {
			$contest_name_link .= '<sup><a style="color:blue" href="/contest/'.$contest['id'].'">'.UOJLocale::get('contests::in progress').'</a></sup>';
		} elseif ($contest['cur_progress'] == CONTEST_PENDING_FINAL_TEST) {
			$contest_name_link .= '<sup><a style="color:blue" href="/contest/'.$contest['id'].'">'.UOJLocale::get('contests::pending final test').'</a></sup>';
		} elseif ($contest['cur_progress'] == CONTEST_TESTING) {
			$contest_name_link .= '<sup><a style="color:blue" href="/contest/'.$contest['id'].'">'.UOJLocale::get('contests::final testing').'</a></sup>';
		} elseif ($contest['cur_progress'] == CONTEST_FINISHED) {
			$contest_name_link .= '<sup><a style="color:grey" href="/contest/'.$contest['id'].'/standings">'.UOJLocale::get('contests::ended').'</a></sup>';
		}
		
		$last_hour = round($contest['last_min'] / 60, 2);
		
		// 列印單行比賽資訊
		$click_zan_block = getClickZanBlock('C', $contest['id'], $contest['zan']);
		echo '<tr>';
		echo '<td>', $contest_name_link, '</td>';
		echo '<td>', '<a href="'.HTML::timeanddate_url($contest['start_time'], array('duration' => $contest['last_min'])).'">'.$contest['start_time_str'].'</a>', '</td>';
		echo '<td>', UOJLocale::get('hours', $last_hour), '</td>';
		echo '<td>', '<a href="/contest/'.$contest['id'].'/registrants"><span class="glyphicon glyphicon-user"></span> &times;'.$contest['player_num'].'</a>', '</td>';
		echo '<td>', '<div class="text-left">'.$click_zan_block.'</div>', '</td>';
		echo '</tr>';
	}

列印比賽列表

列印兩個比賽列表的形式大同小異。以正在進行或即將到來的比賽為例,先整理表頭的HTML程式碼,然後再利用html-lib.php的長列表方法將封裝好的變數方法放入處理

    // 表頭整理
	$table_header = '';
	$table_header .= '<tr>';
	$table_header .= '<th>'.UOJLocale::get('contests::contest name').'</th>';
	$table_header .= '<th style="width:15em;">'.UOJLocale::get('contests::start time').'</th>';
	$table_header .= '<th style="width:100px;">'.UOJLocale::get('contests::duration').'</th>';
	$table_header .= '<th style="width:100px;">'.UOJLocale::get('contests::the number of registrants').'</th>';
	$table_header .= '<th style="width:180px;">'.UOJLocale::get('appraisal').'</th>';
	$table_header .= '</tr>';
	// 生成長表格
	echoLongTable(array('*'), 'contests', "status != 'finished'", 'order by id desc', $table_header,
		echoContest,
		array('page_len' => 100)
	);

附:表格列印

html-lib.php中,使用echoLongTable方法對錶格進行列印

// 重要程式碼:生成長表格的方法,適用於各大需要生成長表格(例如比賽列表、評測列表等)
function echoLongTable($col_names, $table_name, $cond, $tail, $header_row, $print_row, $config) {

	// 初始化分頁配置
	$pag_config = $config;
	$pag_config['col_names'] = $col_names;
	$pag_config['table_name'] = $table_name;
	$pag_config['cond'] = $cond;
	$pag_config['tail'] = $tail;
	$pag = new Paginator($pag_config);

	// 設定表格樣式
	$div_classes = isset($config['div_classes']) ? $config['div_classes'] : array('table-responsive');
	$table_classes = isset($config['table_classes']) ? $config['table_classes'] : array('table', 'table-bordered', 'table-hover', 'table-striped', 'table-text-center');
		
	// 列印表格樣式
	echo '<div class="', join($div_classes, ' '), '">';
	echo '<table class="', join($table_classes, ' '), '">';
	echo '<thead>';
	echo $header_row;
	echo '</thead>';
	echo '<tbody>';

	// 列印表格內容
	foreach ($pag->get() as $idx => $row) {
		if (isset($config['get_row_index'])) {
			$print_row($row, $idx);
		} else {
			$print_row($row);
		}
	}
	if ($pag->isEmpty()) {
		echo '<tr><td colspan="233">'.UOJLocale::get('none').'</td></tr>';
	}

	// 列印表格結尾和分頁控制元件
	echo '</tbody>';
	echo '</table>';
	echo '</div>';
	
	if (isset($config['print_after_table'])) {
		$fun = $config['print_after_table'];
		$fun();
	}
		
	echo $pag->pagination();
}

比賽倒數計時

在即將到來的比賽下,顯示其倒數計時。比賽時間一到給出彈窗資訊

	// 這段用來顯示最近1天內最先要開始的比賽倒數計時,時間一到就會顯示彈窗
	if ($rest_second <= 86400) {
		echo <<<EOD
<div class="text-center bot-buffer-lg">
<div class="text-warning">$upcoming_contest_name 倒數計時</div>
<div id="contest-countdown"></div>
<script type="text/javascript">
$('#contest-countdown').countdown($rest_second, function() {
	if (confirm('$upcoming_contest_name 已經開始了。是否要跳轉到比賽頁面?')) {
		window.location.href = "$upcoming_contest_href";
	} 
});
</script>
</div>
EOD;
	}

比賽內部資訊

由於負責contest_inside.php的資訊內部處理邏輯較多,這裡只取部分主要內部邏輯處理的php程式碼進行解讀

1. echoDashboard 函式

這個函式用於顯示競賽控制檯,包括問題、公告和提交問題的表單。

function echoDashboard() {
	global $contest, $post_notice, $post_question, $reply_question;
	
	$myname = Auth::id();
	$contest_problems = DB::selectAll("select contests_problems.problem_id, best_ac_submissions.submission_id from contests_problems left join best_ac_submissions on contests_problems.problem_id = best_ac_submissions.problem_id and submitter = '{$myname}' where contest_id = {$contest['id']} order by contests_problems.problem_id asc");
	
	for ($i = 0; $i < count($contest_problems); $i++) {
		$contest_problems[$i]['problem'] = queryProblemBrief($contest_problems[$i]['problem_id']);
	}
	
	$contest_notice = DB::selectAll("select * from contests_notice where contest_id = {$contest['id']} order by time desc");
	
	if (Auth::check()) {
		$my_questions = DB::selectAll("select * from contests_asks where contest_id = {$contest['id']} and username = '{$myname}' order by post_time desc");
		$my_questions_pag = new Paginator([
			'data' => $my_questions
		]);
	} else {
		$my_questions_pag = null;
	}
	
	$others_questions_pag = new Paginator([
		'col_names' => array('*'),
		'table_name' => 'contests_asks',
		'cond' => "contest_id = {$contest['id']} and username != '{$myname}' and is_hidden = 0",
		'tail' => 'order by reply_time desc',
		'page_len' => 10
	]);
	
	uojIncludeView('contest-dashboard', [
		'contest' => $contest,
		'contest_notice' => $contest_notice,
		'contest_problems' => $contest_problems,
		'post_question' => $post_question,
		'my_questions_pag' => $my_questions_pag,
		'others_questions_pag' => $others_questions_pag
	]);
}
  • 從資料庫中獲取競賽問題和使用者的最佳提交,並查詢每個問題的簡要資訊。
  • 獲取競賽公告,並按時間倒序排列。
  • 如果使用者已登入,獲取使用者提交的問題並分頁顯示。
  • 獲取其他使用者提交的問題(公開的)並分頁顯示。
  • 包含一個檢視檔案 contest-dashboard,傳遞相關資料進行顯示。

2. echoBackstage 函式

這個函式用於顯示競賽的後臺管理頁面,包括髮布公告和回覆問題的表單。

function echoBackstage() {
	global $contest, $post_notice, $reply_question;
	
	$questions_pag = new Paginator([
		'col_names' => array('*'),
		'table_name' => 'contests_asks',
		'cond' => "contest_id = {$contest['id']}",
		'tail' => 'order by post_time desc',
		'page_len' => 50
	]);
	
	if ($contest['cur_progress'] < CONTEST_TESTING) {
		$contest_data = queryContestData($contest, ['pre_final' => true]);
		calcStandings($contest, $contest_data, $score, $standings);
		
		$standings_data = [
			'contest' => $contest,
			'standings' => $standings,
			'score' => $score,
			'contest_data' => $contest_data
		];
	} else {
		$standings_data = null;
	}
	
	uojIncludeView('contest-backstage', [
		'contest' => $contest,
		'post_notice' => $post_notice,
		'reply_question' => $reply_question,
		'questions_pag' => $questions_pag,
		'standings_data' => $standings_data
	]);
}
  • 獲取並分頁顯示所有競賽問題。
  • 如果競賽尚未進入最終測試階段,計算競賽排名資料。
  • 包含一個檢視檔案 contest-backstage,傳遞相關資料進行顯示。

3. echoMySubmissions 函式

這個函式用於顯示使用者在競賽中的提交記錄,並提供一個選項來顯示所有提交記錄。

function echoMySubmissions() {
	global $contest, $myUser;

	$show_all_submissions_status = Cookie::get('show_all_submissions') !== null ? 'checked="checked" ' : '';
	$show_all_submissions = UOJLocale::get('contests::show all submissions');
	echo <<<EOD
		<div class="checkbox text-right">
			<label for="input-show_all_submissions"><input type="checkbox" id="input-show_all_submissions" $show_all_submissions_status/> $show_all_submissions</label>
		</div>
		<script type="text/javascript">
			$('#input-show_all_submissions').click(function() {
				if (this.checked) {
					$.cookie('show_all_submissions', '');
				} else {
					$.removeCookie('show_all_submissions');
				}
				location.reload();
			});
		</script>
EOD;
	if (Cookie::get('show_all_submissions') !== null) {
		echoSubmissionsList("contest_id = {$contest['id']}", 'order by id desc', array('judge_time_hidden' => ''), $myUser);
	} else {
		echoSubmissionsList("submitter = '{$myUser['username']}' and contest_id = {$contest['id']}", 'order by id desc', array('judge_time_hidden' => ''), $myUser);
	}
}
  • 提供一個核取方塊,使用者可以選擇是否顯示所有提交記錄。
  • 根據使用者選擇,顯示使用者自己的提交記錄或所有提交記錄。

4. echoStandings 函式

這個函式用於顯示競賽的排名情況。

function echoStandings() {
	global $contest;
	
	$contest_data = queryContestData($contest);
	calcStandings($contest, $contest_data, $score, $standings);
	
	uojIncludeView('contest-standings', [
		'contest' => $contest,
		'standings' => $standings,
		'score' => $score,
		'contest_data' => $contest_data
	]);
}
  • 獲取競賽資料並計算排名。
  • 包含一個檢視檔案 contest-standings,傳遞相關資料進行顯示。

5. echoContestCountdown 函式

這個函式用於顯示競賽的倒數計時。

function echoContestCountdown() {
	global $contest;
 	$rest_second = $contest['end_time']->getTimestamp() - UOJTime::$time_now->getTimestamp();
 	$time_str = UOJTime::$time_now_str;
 	$contest_ends_in = UOJLocale::get('contests::contest ends in');
 	echo <<<EOD
 		<div class="card border-info">
 			<div class="card-header bg-info">
 				<h3 class="card-title">$contest_ends_in</h3>
 			</div>
 			<div class="card-body text-center countdown" data-rest="$rest_second"></div>
 		</div>
		<script type="text/javascript">
			checkContestNotice({$contest['id']}, '$time_str');
		</script>
EOD;
}
  • 計算剩餘時間並顯示倒數計時卡片。
  • 包含一個 JavaScript 指令碼用於實時更新倒數計時。

6. echoContestJudgeProgress 函式

這個函式用於顯示競賽的判題進度。

function echoContestJudgeProgress() {
	global $contest;
	if ($contest['cur_progress'] < CONTEST_TESTING) {
		$rop = 0;
		$title = UOJLocale::get('contests::contest pending final test');
	} else {
		$total = DB::selectCount("select count(*) from submissions where contest_id = {$contest['id']}");
		$n_judged = DB::selectCount("select count(*) from submissions where contest_id = {$contest['id']} and status = 'Judged'");
		$rop = $total == 0 ? 100 : (int)($n_judged / $total * 100);
		$title = UOJLocale::get('contests::contest final testing');
	}
	echo <<<EOD
 		<div class="card border-info">
 			<div class="card-header bg-info">
 				<h3 class="card-title">$title</h3>
 			</div>
 			<div class="card-body">
				<div class="progress bot-buffer-no">
					<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="$rop" aria-valuemin="0" aria-valuemax="100" style="width: {$rop}%; min-width: 20px;">{$rop}%</div>
				

	</div>
				</div>
			</div>
		</div>
EOD;
}
  • 根據競賽當前的進度,顯示不同的判題進度條。
  • 包含一個進度條,顯示已判題目的百分比。

7. echoContestFinished 函式

這個函式用於顯示競賽結束的狀態。

function echoContestFinished() {
	$title = UOJLocale::get('contests::contest ended');
	echo <<<EOD
		<div class="card border-info">
			<div class="card-header bg-info">
				<h3 class="card-title">$title</h3>
			</div>
		</div>
EOD;
}
  • 顯示一個簡單的卡片,表明競賽已結束。

附:比賽rating計算

這個評分計算邏輯實現了一種基於排名的評分系統,用於計算參賽者在比賽後的新評分(Rating)。下面是對這段程式碼的詳細解釋:

函式定義

function calcRating($standings, $K = 400) {
    $DELTA = 500;
  • calcRating 是一個計算評分的函式。
  • 引數 $standings 是比賽排名列表。
  • 引數 $K 是一個常數,用於控制評分變化的幅度,預設值為 400。
  • $DELTA 是一個常數,用於計算權重。

獲取參賽人數和初始評分

$n = count($standings);
$rating = array();
for ($i = 0; $i < $n; ++$i) {
    $rating[$i] = $standings[$i][2][1];
}
  • $n 是參賽人數。
  • $rating 陣列儲存每個參賽者的初始評分。

計算排名和名次

$rank = array();
$foot = array();
for ($i = 0; $i < $n; ) {
    $j = $i;
    while ($j + 1 < $n && $standings[$j + 1][3] == $standings[$j][3]) {
        ++$j;
    }
    $our_rk = 0.5 * (($i + 1) + ($j + 1));
    while ($i <= $j) {
        $rank[$i] = $our_rk;
        $foot[$i] = $n - $rank[$i];
        $i++;
    }
}
  • rank 陣列儲存每個參賽者的實際排名。
  • foot 陣列儲存每個參賽者的反向排名(即排名從後往前數的位置)。
  • 這段程式碼計算並儲存參賽者的實際排名,如果多個參賽者的排名相同,他們的實際排名取平均值。

計算權重和期望排名

$weight = array();
for ($i = 0; $i < $n; ++$i) {
    $weight[$i] = pow(7, $rating[$i] / $DELTA);
}
$exp = array_fill(0, $n, 0);
for ($i = 0; $i < $n; ++$i)
    for ($j = 0; $j < $n; ++$j)
        if ($j != $i) {
            $exp[$i] += $weight[$i] / ($weight[$i] + $weight[$j]);
        }
  • weight 陣列儲存每個參賽者的權重,基於其初始評分計算。
  • exp 陣列儲存每個參賽者的期望排名分數,基於所有參賽者之間的權重比計算。

計算新評分

$new_rating = array();
for ($i = 0; $i < $n; $i++) {
    $new_rating[$i] = $rating[$i];
    $new_rating[$i] += ceil($K * ($foot[$i] - $exp[$i]) / ($n - 1));
}
  • new_rating 陣列儲存每個參賽者的新評分。
  • 新評分的計算公式:new_rating = rating + K * (實際排名分數 - 期望排名分數) / (參賽人數 - 1),並取整。

調整新評分

for ($i = $n - 1; $i >= 0; $i--) {
    if ($i + 1 < $n && $standings[$i][3] != $standings[$i + 1][3]) {
        break;
    }
    if ($new_rating[$i] > $rating[$i]) {
        $new_rating[$i] = $rating[$i];
    }
}

for ($i = 0; $i < $n; $i++) {
    if ($new_rating[$i] < 0) {
        $new_rating[$i] = 0;
    }
}
  • 如果排名靠後的參賽者的新評分比初始評分高,將其調整為初始評分。
  • 確保所有新評分不低於零。

返回新評分

return $new_rating;
}
  • 返回計算後的新評分陣列。

比賽後臺資訊管理

對於具有管理員許可權的使用者,需要一個比賽後臺頁面去處理比賽後臺的資訊。這裡使用contest_manage.php去處理其中的頁面邏輯和處理邏輯

這段程式碼是一個 PHP 檔案,通常用於管理競賽相關的設定。讓我們逐段解讀其內部邏輯和功能:

1. 引入必要的 PHP 庫和函式

<?php
	requirePHPLib('form');
  • 使用 requirePHPLib 函式引入了一個名為 form 的 PHP 庫,這個庫可能包含了用於表單處理的函式和類。

2. 驗證和查詢競賽資訊

if (!validateUInt($_GET['id']) || !($contest = queryContest($_GET['id']))) {
	become404Page();
}
  • 首先,驗證了從 GET 請求中獲取的競賽 ID 是否是一個無符號整數,並且查詢對應的競賽資訊。
  • 如果競賽 ID 不合法或者查詢競賽失敗,則呼叫 become404Page() 函式,顯示 404 頁面。

3. 生成更多競賽資訊

genMoreContestInfo($contest);
  • 呼叫 genMoreContestInfo 函式,可能用於生成和展示更多關於競賽的詳細資訊,但具體實現不在這段程式碼中。

4. 許可權檢查

if (!isSuperUser($myUser)) {
	become403Page();
}
  • 判斷當前使用者是否為超級使用者(管理員)。如果不是,則呼叫 become403Page() 函式,顯示 403 頁面,表示許可權不足。

5. 建立並處理比賽時間表單 time_form

$time_form = new UOJForm('time');
// 新增輸入欄位及其驗證規則和處理邏輯
$time_form->runAtServer();
  • 建立了一個名為 time_form 的表單物件,用於編輯競賽的開始時間、時長和名稱。
  • 設定了各欄位的初始值和驗證函式,以及處理函式。

6. 建立和處理管理者表單 managers_form 和題目表單 problems_form

$managers_form = newAddDelCmdForm('managers', ...);
$problems_form = newAddDelCmdForm('problems', ...);
$managers_form->runAtServer();
$problems_form->runAtServer();
  • 建立了兩個表單物件 managers_formproblems_form,用於新增和刪除競賽的管理員和題目。
  • 設定了驗證函式和處理函式,並執行了表單處理操作。

7. 超級使用者特有功能表單處理

if (isSuperUser($myUser)) {
	// 建立和處理額外的表單,如評分變化上限、比賽型別、排名版本等
}
  • 如果當前使用者是超級使用者,建立和處理了額外的表單,用於編輯評分變化上限、比賽型別和排名版本等競賽的附加設定。

8. 執行表單處理邏輯

$time_form->runAtServer();
$managers_form->runAtServer();
$problems_form->runAtServer();
  • 最後,依次執行了所有表單物件的 runAtServer() 方法,處理提交的資料並更新資料庫。

比賽報名人員資訊顯示

這段程式碼主要用於展示競賽的報名參賽者資訊,並提供一些管理功能。讓我們逐段解讀其功能和邏輯:

1. 處理未開始競賽的邏輯

if ($contest['cur_progress'] == CONTEST_NOT_STARTED) {
    // 判斷使用者是否已經報名
    $iHasRegistered = $myUser != null && hasRegistered($myUser, $contest);

    if ($iHasRegistered) {
        // 建立取消報名的表單
        $unregister_form = new UOJForm('unregister');
        $unregister_form->handle = function() {
            global $myUser, $contest;
            DB::query("delete from contests_registrants where username = '{$myUser['username']}' and contest_id = {$contest['id']}");
            updateContestPlayerNum($contest);
        };
        $unregister_form->submit_button_config['class_str'] = 'btn btn-danger btn-xs';
        $unregister_form->submit_button_config['text'] = '取消報名';
        $unregister_form->succ_href = "/contests";
        $unregister_form->runAtServer();
    }
    
    if ($has_contest_permission) {
        // 建立重新計算參賽前 rating 的表單
        $pre_rating_form = new UOJForm('pre_rating');
        $pre_rating_form->handle = function() {
            global $contest;
            foreach (DB::selectAll("select * from contests_registrants where contest_id = {$contest['id']}") as $reg) {
                $user = queryUser($reg['username']);
                DB::update("update contests_registrants set user_rating = {$user['rating']} where contest_id = {$contest['id']} and username = '{$user['username']}'");
            }
        };
        $pre_rating_form->submit_button_config['align'] = 'right';
        $pre_rating_form->submit_button_config['class_str'] = 'btn btn-warning';
        $pre_rating_form->submit_button_config['text'] = '重新計算參賽前的 rating';
        $pre_rating_form->submit_button_config['smart_confirm'] = '';
        
        $pre_rating_form->runAtServer();
    }
}
  • 如果競賽尚未開始 ($contest['cur_progress'] == CONTEST_NOT_STARTED),則執行以下邏輯:
    • 檢查當前使用者是否已經報名。
    • 如果已經報名,則建立一個用於取消報名的表單,並處理取消報名的邏輯。
    • 如果當前使用者有管理競賽的許可權 ($has_contest_permission),則建立一個用於重新計算參賽前評分 (user_rating) 的表單,並處理相應邏輯。

2. 頁面頭部和競賽標題展示

<?php echoUOJPageHeader(HTML::stripTags($contest['name']) . ' - ' . UOJLocale::get('contests::contest registrants')) ?>

<h1 class="text-center"><?= $contest['name'] ?></h1>
  • 使用 echoUOJPageHeader 函式顯示頁面的頭部,包括競賽的標題和“競賽參賽者”等翻譯後的本地化文字。

3. 根據競賽狀態顯示報名資訊

<?php if ($contest['cur_progress'] == CONTEST_NOT_STARTED): ?>
    <?php if ($iHasRegistered): ?>
        <div class="float-right">
            <?php $unregister_form->printHTML(); ?>
        </div>
        <div><a style="color:green">已報名</a></div>
    <?php else: ?>
        <div>當前尚未報名,您可以<a style="color:red" href="/contest/<?= $contest['id'] ?>/register">報名</a>。</div>
    <?php endif ?>
<div class="top-buffer-sm"></div>
<?php endif ?>
  • 如果競賽尚未開始,根據使用者是否已經報名顯示不同的資訊:
    • 如果已經報名,則顯示取消報名按鈕。
    • 如果尚未報名,則顯示報名連結。

4. 顯示參賽者列表

<?php
if ($show_ip) {
    // 如果有許可權顯示 IP 地址,則設定表頭
    $header_row = '<tr><th>#</th><th>'.UOJLocale::get('username').'</th><th>remote_addr</th><th>rating</th></tr>';
    $ip_owner = array();
    foreach (DB::selectAll("select * from contests_registrants where contest_id = {$contest['id']} order by user_rating asc") as $reg) {
        $user = queryUser($reg['username']);
        $ip_owner[$user['remote_addr']] = $reg['username'];
    }
} else {
    // 否則只顯示使用者名稱和評分
    $header_row = '<tr><th>#</th><th>'.UOJLocale::get('username').'</th><th>rating</th></tr>';
}

echoLongTable(array('*'), 'contests_registrants', "contest_id = {$contest['id']}", 'order by user_rating desc',
    $header_row,
    function($contest, $num) {
        global $myUser;
        global $show_ip, $ip_owner;
        $user = queryUser($contest['username']);
        $user_link = getUserLink($contest['username'], $contest['user_rating']);
        if (!$show_ip) {
            echo '<tr>';
        } else {
            if ($ip_owner[$user['remote_addr']] != $user['username']) {
                echo '<tr class="danger">';
            } else {
                echo '<tr>';
            }
        }
        echo '<td>'.$num.'</td>';
        echo '<td>'.$user_link.'</td>';
        if ($show_ip) {
            echo '<td>'.$user['remote_addr'].'</td>';
        }
        echo '<td>'.$contest['user_rating'].'</td>';
        echo '</tr>';
    },
    array('page_len' => 100,
        'get_row_index' => '',
        'print_after_table' => function() {
            global $pre_rating_form;
            if (isset($pre_rating_form)) {
                $pre_rating_form->printHTML();
            }
        }
    )
);
?>
  • 使用 echoLongTable 函式顯示參賽者列表,根據是否有許可權顯示 IP 地址決定表格的表頭和樣式。
  • 對每個參賽者生成一行表格資料,並根據許可權高亮顯示 IP 地址不匹配的參賽者。
  • 如果有重新計算評分的表單 ($pre_rating_form),則在表格後面顯示該表單。

比賽報名頁面

contest_registration.php實現了一個簡單的競賽報名頁面,使用者必須登入後才能進行報名操作。如果使用者已經報名或具有管理許可權,或者競賽不是未開始狀態,則會被重定向到競賽列表頁面。頁面展示了比賽的規則,並提供了一個表單用於使用者提交報名資訊,成功報名後將跳轉回競賽列表頁面。

1. 許可權檢查和重定向

if ($myUser == null) {
    redirectToLogin();
} elseif (hasContestPermission($myUser, $contest) || hasRegistered($myUser, $contest) || $contest['cur_progress'] != CONTEST_NOT_STARTED) {
    redirectTo('/contests');
}
  • 檢查當前使用者是否已登入 ($myUser == null),如果未登入,則重定向到登入頁面。
  • 如果使用者已經具有競賽的管理許可權或已經報名了該競賽,或者競賽當前不是未開始狀態,則重定向到競賽列表頁面 (/contests)。

2. 處理報名表單

$register_form = new UOJForm('register');
$register_form->handle = function() {
    global $myUser, $contest;
    DB::query("insert into contests_registrants (username, user_rating, contest_id, has_participated) values ('{$myUser['username']}', {$myUser['rating']}, {$contest['id']}, 0)");
    updateContestPlayerNum($contest);
};
$register_form->submit_button_config['class_str'] = 'btn btn-primary';
$register_form->submit_button_config['text'] = '報名比賽';
$register_form->succ_href = "/contests";

$register_form->runAtServer();
  • 建立一個名為 register 的表單物件 UOJForm,用於使用者報名操作。
  • 定義了表單的處理函式 ($register_form->handle),在使用者點選提交時執行資料庫操作,將使用者資訊插入到 contests_registrants 表中,並更新競賽的參賽人數。
  • 設定提交按鈕的樣式和文字,並定義成功後的重定向連結為 /contests
  • 使用 runAtServer() 方法執行表單處理,處理使用者的提交請求。

3. 頁面頭部

<?php echoUOJPageHeader(HTML::stripTags($contest['name']) . ' - 報名') ?>
  • 使用 echoUOJPageHeader 函式顯示頁面的頭部,包括競賽的標題和“報名”等文字。

4. 比賽規則展示

<h1 class="page-header">比賽規則</h1>
<ul>
    <!-- 比賽規則的具體內容 -->
</ul>
  • 展示了關於比賽的規則,以無序列表的形式呈現。

5. 顯示報名表單

<?php $register_form->printHTML(); ?>
  • 使用 printHTML() 方法將報名表單的 HTML 程式碼輸出到頁面中,讓使用者可以填寫和提交報名資訊。

6. 頁面底部

<?php echoUOJPageFooter() ?>
  • 使用 echoUOJPageFooter 函式顯示頁面的底部,包括版權資訊等。

相關文章