561
4.cpp
Normal file
561
4.cpp
Normal file
@@ -0,0 +1,561 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <random>
|
||||||
|
#include <ctime>
|
||||||
|
#include <map>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// --- 定義遊戲常數與列舉 ---
|
||||||
|
|
||||||
|
enum CardType {
|
||||||
|
COLOR_CARD,
|
||||||
|
JOKER,
|
||||||
|
PLUS_2,
|
||||||
|
LAST_ROUND_CARD
|
||||||
|
};
|
||||||
|
|
||||||
|
// 7種顏色
|
||||||
|
enum Color {
|
||||||
|
BROWN = 0, BLUE, GRAY, GREEN, ORANGE, PINK, YELLOW, NONE
|
||||||
|
};
|
||||||
|
|
||||||
|
const string COLOR_NAMES[] = {
|
||||||
|
"Brown", "Blue", "Gray", "Green", "Orange", "Pink", "Yellow", "None"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分數表 (對應 0, 1, 2, 3, 4, 5, 6+ 張牌的分數)
|
||||||
|
const int SCORE_TABLE[] = {0, 1, 3, 6, 10, 15, 21};
|
||||||
|
|
||||||
|
struct Card {
|
||||||
|
CardType type;
|
||||||
|
Color color;
|
||||||
|
|
||||||
|
// 顯示卡片資訊
|
||||||
|
string toString() const {
|
||||||
|
if (type == LAST_ROUND_CARD) return "[LAST ROUND]";
|
||||||
|
if (type == PLUS_2) return "[+2]";
|
||||||
|
if (type == JOKER) return "[Joker]";
|
||||||
|
return "[" + COLOR_NAMES[color] + "]";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 玩家類別 ---
|
||||||
|
|
||||||
|
class Player {
|
||||||
|
public:
|
||||||
|
int id;
|
||||||
|
bool isHuman;
|
||||||
|
bool hasTakenRow; // 本回合是否已拿牌
|
||||||
|
vector<Card> hand;
|
||||||
|
|
||||||
|
Player(int _id, bool _human) : id(_id), isHuman(_human), hasTakenRow(false) {}
|
||||||
|
|
||||||
|
// 計算分數
|
||||||
|
int calculateScore() const {
|
||||||
|
map<Color, int> colorCounts;
|
||||||
|
int jokerCount = 0;
|
||||||
|
int plus2Count = 0;
|
||||||
|
|
||||||
|
for (const auto& card : hand) {
|
||||||
|
if (card.type == COLOR_CARD) colorCounts[card.color]++;
|
||||||
|
else if (card.type == JOKER) jokerCount++;
|
||||||
|
else if (card.type == PLUS_2) plus2Count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 簡單的貪婪演算法分配 Joker:
|
||||||
|
// 每次將 Joker 分配給「能獲得最大分數增益」的顏色
|
||||||
|
// 如果該顏色已達 6 張(21分),則不再增加效益,改分配給其他顏色
|
||||||
|
for (int i = 0; i < jokerCount; i++) {
|
||||||
|
Color bestColor = NONE;
|
||||||
|
int maxGain = -1;
|
||||||
|
|
||||||
|
// 嘗試加到現有的顏色堆中
|
||||||
|
for (auto const& [color, count] : colorCounts) {
|
||||||
|
int currentScore = (count >= 6) ? 21 : SCORE_TABLE[count];
|
||||||
|
int nextScore = (count + 1 >= 6) ? 21 : SCORE_TABLE[count + 1];
|
||||||
|
int gain = nextScore - currentScore;
|
||||||
|
|
||||||
|
if (gain > maxGain) {
|
||||||
|
maxGain = gain;
|
||||||
|
bestColor = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果沒有顏色牌,或者加新顏色(從0變1得1分)效益更好
|
||||||
|
if (maxGain < 1) {
|
||||||
|
// 這裡簡化處理:若效益小於1(通常是現有牌都滿6張),
|
||||||
|
// 實際上應該創造一個新顏色堆,但這裡我們假設 Joker 加到任意堆
|
||||||
|
// 為了程式邏輯簡單,我們找一個目前數量最少的顏色加進去,或是視為新顏色
|
||||||
|
// 在此邏輯下,直接視為一個新顏色(1分)
|
||||||
|
// 為了實作方便,我們不修改 map key,只記錄分數即可,但為了顯示正確,我們需要模擬
|
||||||
|
// 這裡暫時不實作極端複雜的 Joker 分配,採用:
|
||||||
|
// "優先補滿未滿6張的最高分堆,若都滿則開新堆"
|
||||||
|
// 簡化版:直接加到第一種顏色,計分邏輯會在後面排序處理
|
||||||
|
if (colorCounts.empty()) {
|
||||||
|
// 沒牌時隨便選個顏色當作 Joker 變成的顏色
|
||||||
|
colorCounts[BROWN]++;
|
||||||
|
} else {
|
||||||
|
// 加到目前張數最多但 < 6 的堆,若都 >=6 則隨便加
|
||||||
|
Color target = colorCounts.begin()->first;
|
||||||
|
int maxC = -1;
|
||||||
|
for(auto const& [c, count] : colorCounts){
|
||||||
|
if(count < 6 && count > maxC) { maxC = count; target = c; }
|
||||||
|
}
|
||||||
|
colorCounts[target]++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
colorCounts[bestColor]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 計算各顏色分數並排序
|
||||||
|
vector<int> scores;
|
||||||
|
for (auto const& [color, count] : colorCounts) {
|
||||||
|
scores.push_back((count >= 6) ? 21 : SCORE_TABLE[count]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 補齊不足 7 種顏色為 0 分,方便排序
|
||||||
|
while(scores.size() < 7) scores.push_back(0);
|
||||||
|
|
||||||
|
// 由大到小排序
|
||||||
|
sort(scores.rbegin(), scores.rend());
|
||||||
|
|
||||||
|
int totalScore = 0;
|
||||||
|
// 前三高分為正分
|
||||||
|
for (int i = 0; i < 3; i++) totalScore += scores[i];
|
||||||
|
// 其餘為負分
|
||||||
|
for (int i = 3; i < scores.size(); i++) totalScore -= scores[i];
|
||||||
|
|
||||||
|
// 加上 +2 卡
|
||||||
|
totalScore += (plus2Count * 2);
|
||||||
|
|
||||||
|
return totalScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printHand() const {
|
||||||
|
cout << "P" << id << (isHuman ? "(YOU)" : "(BOT)") << " Hand: ";
|
||||||
|
if (hand.empty()) {
|
||||||
|
cout << "Empty";
|
||||||
|
} else {
|
||||||
|
// 整理顯示,例如: Blue:3, Green:1, +2:1...
|
||||||
|
map<string, int> summary;
|
||||||
|
for(const auto& c : hand) summary[c.toString()]++;
|
||||||
|
|
||||||
|
for(const auto& item : summary) {
|
||||||
|
cout << item.first << "x" << item.second << " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cout << " | Current Est. Score: " << calculateScore() << endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 遊戲控制類別 ---
|
||||||
|
|
||||||
|
class ColorettoGame {
|
||||||
|
private:
|
||||||
|
int numPlayers;
|
||||||
|
vector<Player> players;
|
||||||
|
vector<Card> deck;
|
||||||
|
vector<vector<Card>> rows; // 場上的牌列
|
||||||
|
bool lastRoundTriggered;
|
||||||
|
bool isGameEnded;
|
||||||
|
int startPlayerIndex;
|
||||||
|
|
||||||
|
// 初始化牌堆
|
||||||
|
void initDeck() {
|
||||||
|
deck.clear();
|
||||||
|
// 7種顏色,每種9張
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
// 3人遊戲時移除一種顏色(依規則),但作業預設4人,此處寫通用邏輯
|
||||||
|
if (numPlayers == 3 && i == 6) break;
|
||||||
|
|
||||||
|
for (int j = 0; j < 9; j++) {
|
||||||
|
deck.push_back({COLOR_CARD, (Color)i});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// +2卡 10張
|
||||||
|
for (int i = 0; i < 10; i++) deck.push_back({PLUS_2, NONE});
|
||||||
|
// Joker 3張
|
||||||
|
for (int i = 0; i < 3; i++) deck.push_back({JOKER, NONE});
|
||||||
|
|
||||||
|
shuffleDeck();
|
||||||
|
|
||||||
|
// 插入「最後回合卡」至倒數第16張
|
||||||
|
if (deck.size() >= 16) {
|
||||||
|
deck.insert(deck.end() - 15, {LAST_ROUND_CARD, NONE});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void shuffleDeck() {
|
||||||
|
random_device rd;
|
||||||
|
mt19937 g(rd());
|
||||||
|
shuffle(deck.begin(), deck.end(), g);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 發初始手牌
|
||||||
|
void dealInitialCards() {
|
||||||
|
cout << "\n--- Dealing Initial Cards ---\n";
|
||||||
|
vector<Color> usedColors;
|
||||||
|
|
||||||
|
for (auto& p : players) {
|
||||||
|
bool valid = false;
|
||||||
|
// 尋找一張符合條件的牌 (變色龍牌,且顏色不重複)
|
||||||
|
// 這裡為了簡單,直接遍歷牌堆找,找到就移出
|
||||||
|
for (auto it = deck.begin(); it != deck.end(); ++it) {
|
||||||
|
if (it->type == COLOR_CARD) {
|
||||||
|
bool colorUsed = false;
|
||||||
|
for(Color c : usedColors) if(c == it->color) colorUsed = true;
|
||||||
|
|
||||||
|
if (!colorUsed) {
|
||||||
|
p.hand.push_back(*it);
|
||||||
|
usedColors.push_back(it->color);
|
||||||
|
deck.erase(it);
|
||||||
|
valid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 理論上牌堆剛開始一定夠,不做例外處理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
ColorettoGame() : lastRoundTriggered(false), isGameEnded(false), startPlayerIndex(0) {
|
||||||
|
srand(time(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(int n) {
|
||||||
|
numPlayers = n;
|
||||||
|
players.clear();
|
||||||
|
rows.clear();
|
||||||
|
rows.resize(numPlayers); // 列數等於玩家數
|
||||||
|
|
||||||
|
// 建立玩家,P1 為人類
|
||||||
|
players.push_back(Player(1, true));
|
||||||
|
for (int i = 2; i <= numPlayers; i++) {
|
||||||
|
players.push_back(Player(i, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
initDeck();
|
||||||
|
dealInitialCards();
|
||||||
|
startPlayerIndex = rand() % numPlayers; // 隨機起始玩家
|
||||||
|
}
|
||||||
|
|
||||||
|
// 抽一張牌,處理 Last Round 邏輯
|
||||||
|
Card drawCard() {
|
||||||
|
if (deck.empty()) return {LAST_ROUND_CARD, NONE}; // 防禦性編程
|
||||||
|
|
||||||
|
Card c = deck.front();
|
||||||
|
deck.erase(deck.begin());
|
||||||
|
|
||||||
|
if (c.type == LAST_ROUND_CARD) {
|
||||||
|
cout << "\n!!! LAST ROUND CARD DRAWN !!! The game will end after this round.\n";
|
||||||
|
lastRoundTriggered = true;
|
||||||
|
// 規則:抽到最後回合卡,該玩家重抽一張
|
||||||
|
if (deck.empty()) return c; // 沒牌了就只能停
|
||||||
|
return drawCard();
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printBoard() {
|
||||||
|
cout << "\n================ BOARD STATE ================\n";
|
||||||
|
cout << "Deck remaining: " << deck.size() << " cards\n";
|
||||||
|
if (lastRoundTriggered) cout << "WARNING: This is the LAST ROUND!\n";
|
||||||
|
|
||||||
|
cout << "\n--- Rows ---\n";
|
||||||
|
for (int i = 0; i < numPlayers; i++) {
|
||||||
|
cout << "Row " << (i + 1) << ": ";
|
||||||
|
if (rows[i].empty()) {
|
||||||
|
// 如果該列已被拿走 (標記邏輯:這裡我們用特殊的空狀態表示)
|
||||||
|
// 但為了簡單,我們可以用一個變數紀錄列是否被拿走
|
||||||
|
// 在此實作中,我們檢查 rows[i] 是否空。
|
||||||
|
// *注意*:規則是拿走列後,列變空,玩家退出回合。
|
||||||
|
// 為了區分「剛開始空」和「被拿走」,我們需要邏輯判斷。
|
||||||
|
// 這裡簡化:顯示內容即可。只有當有人要拿牌時才判斷是否為空。
|
||||||
|
cout << "(Empty)";
|
||||||
|
} else {
|
||||||
|
for (const auto& card : rows[i]) {
|
||||||
|
cout << card.toString() << " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 檢查這列是否還能放 (最多3張)
|
||||||
|
if (rows[i].size() >= 3) cout << " [FULL]";
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "\n--- Players ---\n";
|
||||||
|
for (const auto& p : players) {
|
||||||
|
cout << (p.hasTakenRow ? "[OUT] " : "[IN] ");
|
||||||
|
p.printHand();
|
||||||
|
}
|
||||||
|
cout << "=============================================\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查是否所有人都拿完牌了
|
||||||
|
bool isRoundOver() {
|
||||||
|
for (const auto& p : players) {
|
||||||
|
if (!p.hasTakenRow) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行一個回合
|
||||||
|
void playRound() {
|
||||||
|
// 重置玩家狀態和場上的列
|
||||||
|
for (auto& p : players) p.hasTakenRow = false;
|
||||||
|
for (auto& r : rows) r.clear(); // 清空場上的牌
|
||||||
|
|
||||||
|
int currentPlayerIdx = startPlayerIndex;
|
||||||
|
|
||||||
|
cout << "\n>>> NEW ROUND START! Player " << players[currentPlayerIdx].id << " goes first. <<<\n";
|
||||||
|
|
||||||
|
while (!isRoundOver()) {
|
||||||
|
Player& currP = players[currentPlayerIdx];
|
||||||
|
|
||||||
|
// 如果該玩家已經拿過牌,跳過
|
||||||
|
if (currP.hasTakenRow) {
|
||||||
|
currentPlayerIdx = (currentPlayerIdx + 1) % numPlayers;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顯示盤面 (只在人類回合前顯示,避免洗版,或每步都顯示)
|
||||||
|
if (currP.isHuman) printBoard();
|
||||||
|
|
||||||
|
// --- 動作選擇 ---
|
||||||
|
// 判斷可用動作
|
||||||
|
bool canDraw = (deck.size() > 0);
|
||||||
|
// 檢查是否所有列都滿了,如果全滿就不能抽牌
|
||||||
|
bool allRowsFull = true;
|
||||||
|
bool allRowsEmptyOrTaken = true; // 用於檢查能不能拿牌
|
||||||
|
|
||||||
|
// 檢查列的狀態
|
||||||
|
// 這裡有個細節:被拿走的列在程式邏輯中是空的,但不能再放牌。
|
||||||
|
// 為了簡化,我們需要知道哪些列是「此回合已被拿走」的。
|
||||||
|
// 由於規則:拿走列的人就退出了。所以剩下的玩家面對的列一定是「還沒被拿走的」。
|
||||||
|
// 但剩下的列可能是空的。
|
||||||
|
// 修正:我們需要一個狀態來標記 Row 是否被拿走嗎?
|
||||||
|
// 實際上,剩餘玩家人數 = 剩餘未被拿走的列數。
|
||||||
|
// 所以只要列還沒滿(3張),且還有人在場,就可以抽牌。
|
||||||
|
|
||||||
|
// 簡化判定:只要有任一列牌數 < 3,就可以抽牌放置。
|
||||||
|
allRowsFull = true;
|
||||||
|
for(const auto& r : rows) {
|
||||||
|
// 這邊有個邏輯問題:如果不追蹤哪個列屬於哪個被拿走的,會很難判斷。
|
||||||
|
// Coloretto 規則:每位玩家拿走一列。列的數量 = 玩家數量。
|
||||||
|
// 所以,如果 rows[i] 被拿走了,我們應該將其鎖定。
|
||||||
|
// 為了方便,我們用一個 vector<bool> rowTaken 來記錄本回合狀態。
|
||||||
|
}
|
||||||
|
// 我們需要在 playRound 內部維護 rowTaken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 為了邏輯清晰,重寫 playRound 流程控制
|
||||||
|
void gameLoop() {
|
||||||
|
while (!isGameEnded) {
|
||||||
|
// --- 回合初始化 ---
|
||||||
|
for (auto& r : rows) r.clear();
|
||||||
|
for (auto& p : players) p.hasTakenRow = false;
|
||||||
|
vector<bool> rowTaken(numPlayers, false); // 紀錄哪些列被拿走了
|
||||||
|
int playersOutCount = 0;
|
||||||
|
|
||||||
|
int currentPlayerIdx = startPlayerIndex;
|
||||||
|
cout << "\n>>> NEW ROUND START! <<<\n";
|
||||||
|
|
||||||
|
// --- 回合行動迴圈 ---
|
||||||
|
while (playersOutCount < numPlayers) {
|
||||||
|
Player& currP = players[currentPlayerIdx];
|
||||||
|
|
||||||
|
// 若玩家已出局,換下一位
|
||||||
|
if (currP.hasTakenRow) {
|
||||||
|
currentPlayerIdx = (currentPlayerIdx + 1) % numPlayers;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(currP.isHuman) printBoard();
|
||||||
|
|
||||||
|
// 檢查合法動作
|
||||||
|
// 1. 抽牌: 牌堆有牌 AND 至少有一列沒被拿走且沒滿
|
||||||
|
bool canDraw = !deck.empty();
|
||||||
|
bool anyRowPlaceable = false;
|
||||||
|
for(int i=0; i<numPlayers; i++) {
|
||||||
|
if (!rowTaken[i] && rows[i].size() < 3) anyRowPlaceable = true;
|
||||||
|
}
|
||||||
|
if (!anyRowPlaceable) canDraw = false;
|
||||||
|
|
||||||
|
// 2. 拿牌: 至少有一列沒被拿走且裡面有牌 (規則: 不能拿空列,除非全都是空列且無法執行動作? 不,規則說選擇收集時區域至少要有一張)
|
||||||
|
bool canTake = false;
|
||||||
|
for(int i=0; i<numPlayers; i++) {
|
||||||
|
if (!rowTaken[i] && rows[i].size() > 0) canTake = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 強制邏輯:如果不能抽牌(例如全滿),必須拿牌。
|
||||||
|
// 如果不能拿牌(全都空),必須抽牌。
|
||||||
|
|
||||||
|
int action = 0; // 1: Draw, 2: Take
|
||||||
|
|
||||||
|
if (currP.isHuman) {
|
||||||
|
cout << "\nPlayer " << currP.id << " (YOU), choose action:\n";
|
||||||
|
if (canDraw) cout << "1. Draw a card\n";
|
||||||
|
if (canTake) cout << "2. Take a row\n";
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
cout << "> ";
|
||||||
|
if (!(cin >> action)) { cin.clear(); cin.ignore(1000, '\n'); continue; }
|
||||||
|
if (action == 1 && canDraw) break;
|
||||||
|
if (action == 2 && canTake) break;
|
||||||
|
cout << "Invalid action. Try again.\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 電腦 AI
|
||||||
|
// 簡單隨機:如果能做就隨機選,若只能做一種就做那種
|
||||||
|
vector<int> possibleActions;
|
||||||
|
if (canDraw) possibleActions.push_back(1);
|
||||||
|
if (canTake) possibleActions.push_back(2);
|
||||||
|
action = possibleActions[rand() % possibleActions.size()];
|
||||||
|
cout << "Player " << currP.id << " (Bot) chooses action " << action << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 執行動作 ---
|
||||||
|
if (action == 1) {
|
||||||
|
// 抽牌
|
||||||
|
Card c = drawCard();
|
||||||
|
cout << "Draws: " << c.toString() << endl;
|
||||||
|
|
||||||
|
// 選擇放哪一列
|
||||||
|
int rowIdx = -1;
|
||||||
|
if (currP.isHuman) {
|
||||||
|
cout << "Select a row to place card (";
|
||||||
|
for(int i=0; i<numPlayers; i++) {
|
||||||
|
if(!rowTaken[i] && rows[i].size() < 3) cout << (i+1) << " ";
|
||||||
|
}
|
||||||
|
cout << "): ";
|
||||||
|
while(true) {
|
||||||
|
if(cin >> rowIdx) {
|
||||||
|
rowIdx--; // 轉 0-base
|
||||||
|
if (rowIdx >= 0 && rowIdx < numPlayers && !rowTaken[rowIdx] && rows[rowIdx].size() < 3) break;
|
||||||
|
} else { cin.clear(); cin.ignore(1000, '\n'); }
|
||||||
|
cout << "Invalid row. Must be not taken and not full (<3). Try again: ";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// AI 隨機放
|
||||||
|
vector<int> validRows;
|
||||||
|
for(int i=0; i<numPlayers; i++) {
|
||||||
|
if (!rowTaken[i] && rows[i].size() < 3) validRows.push_back(i);
|
||||||
|
}
|
||||||
|
rowIdx = validRows[rand() % validRows.size()];
|
||||||
|
cout << "Bot places card in Row " << (rowIdx + 1) << endl;
|
||||||
|
}
|
||||||
|
rows[rowIdx].push_back(c);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 拿牌
|
||||||
|
int rowIdx = -1;
|
||||||
|
if (currP.isHuman) {
|
||||||
|
cout << "Select a row to take (";
|
||||||
|
for(int i=0; i<numPlayers; i++) {
|
||||||
|
if(!rowTaken[i] && rows[i].size() > 0) cout << (i+1) << " ";
|
||||||
|
}
|
||||||
|
cout << "): ";
|
||||||
|
while(true) {
|
||||||
|
if(cin >> rowIdx) {
|
||||||
|
rowIdx--;
|
||||||
|
if (rowIdx >= 0 && rowIdx < numPlayers && !rowTaken[rowIdx] && rows[rowIdx].size() > 0) break;
|
||||||
|
} else { cin.clear(); cin.ignore(1000, '\n'); }
|
||||||
|
cout << "Invalid row. Must be not taken and have cards. Try again: ";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// AI 隨機拿
|
||||||
|
vector<int> validRows;
|
||||||
|
for(int i=0; i<numPlayers; i++) {
|
||||||
|
if (!rowTaken[i] && rows[i].size() > 0) validRows.push_back(i);
|
||||||
|
}
|
||||||
|
rowIdx = validRows[rand() % validRows.size()];
|
||||||
|
cout << "Bot takes Row " << (rowIdx + 1) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行拿牌邏輯
|
||||||
|
cout << "Player " << currP.id << " took Row " << (rowIdx + 1) << " with " << rows[rowIdx].size() << " cards.\n";
|
||||||
|
currP.hand.insert(currP.hand.end(), rows[rowIdx].begin(), rows[rowIdx].end());
|
||||||
|
rowTaken[rowIdx] = true; // 標記該列被拿走
|
||||||
|
currP.hasTakenRow = true; // 玩家退出本回合
|
||||||
|
playersOutCount++;
|
||||||
|
|
||||||
|
// 最後一個拿牌的成為下回合起始玩家
|
||||||
|
if (playersOutCount == numPlayers) {
|
||||||
|
startPlayerIndex = currentPlayerIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 換下一位
|
||||||
|
currentPlayerIdx = (currentPlayerIdx + 1) % numPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回合結束檢查
|
||||||
|
if (lastRoundTriggered) {
|
||||||
|
isGameEnded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finalScoring();
|
||||||
|
}
|
||||||
|
|
||||||
|
void finalScoring() {
|
||||||
|
cout << "\n\n================ GAME OVER ================\n";
|
||||||
|
cout << "Calculating scores...\n";
|
||||||
|
|
||||||
|
// 用 pair 存 (分數, 玩家ID) 以便排序
|
||||||
|
vector<pair<int, int>> rank;
|
||||||
|
|
||||||
|
for (const auto& p : players) {
|
||||||
|
int score = p.calculateScore();
|
||||||
|
rank.push_back({score, p.id});
|
||||||
|
cout << "Player " << p.id << (p.isHuman ? " (YOU)" : " (Bot)") << " Hand:\n";
|
||||||
|
// 顯示最終手牌統計
|
||||||
|
map<string, int> summary;
|
||||||
|
for(const auto& c : p.hand) summary[c.toString()]++;
|
||||||
|
for(const auto& item : summary) cout << " " << item.first << " x" << item.second;
|
||||||
|
cout << "\n -> Final Score: " << score << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sort(rank.rbegin(), rank.rend());
|
||||||
|
|
||||||
|
cout << "--- FINAL STANDINGS ---\n";
|
||||||
|
for (int i = 0; i < rank.size(); i++) {
|
||||||
|
cout << (i+1) << ". Player " << rank[i].second << " : " << rank[i].first << " points";
|
||||||
|
if (rank[i].second == 1) cout << " <--- YOU";
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
cout << "Welcome to Coloretto (C++ Version)!\n";
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
int numP = 4;
|
||||||
|
cout << "Enter number of players (3-5) [Default 4]: ";
|
||||||
|
if (cin >> numP) {
|
||||||
|
if (numP < 3 || numP > 5) numP = 4;
|
||||||
|
} else {
|
||||||
|
cin.clear(); cin.ignore(1000, '\n');
|
||||||
|
numP = 4;
|
||||||
|
}
|
||||||
|
cout << "Starting game with " << numP << " players (1 Human vs " << (numP-1) << " Bots).\n";
|
||||||
|
|
||||||
|
ColorettoGame game;
|
||||||
|
game.setup(numP);
|
||||||
|
game.gameLoop();
|
||||||
|
|
||||||
|
cout << "\nPlay again? (y/n): ";
|
||||||
|
char c;
|
||||||
|
cin >> c;
|
||||||
|
if (c != 'y' && c != 'Y') break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user