// This file is part of Timmi. // // Timmi is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. #include #include #include #include #include //---------------------------- // GameState related functions void apply_move(GameState &gs, const Move &move) { int i = 0; TableCard tc; switch (move.movetype) { case PICKUP: for (i = 0; i < gs.table.size(); i++) { gs.players[move.player].hand.push_back(unassign_joker(gs.table.at(i).tobeat)); if (is_card(gs.table.at(i).beater)) { gs.players[move.player].hand.push_back(unassign_joker(gs.table.at(i).beater)); } } gs.table.clear(); fill_hands(gs); gs.turn = next_turn(gs); break; case HIT: if (gs.turn == move.player) { fill_hands(gs); gs.turn = next_turn(gs); } tc.tobeat = move.owncard; tc.beater.suit = NOSUIT; tc.beater.rank = NORANK; gs.table.push_back(tc); break; case BEAT: for (i = 0; i < gs.table.size(); i++) { if (gs.table.at(i).tobeat == move.othercard && !is_card(gs.table.at(i).beater)) { gs.table.at(i).beater = move.owncard; break; } } break; case MOVE: tc.tobeat = move.owncard; tc.beater.suit = NOSUIT; tc.beater.rank = NORANK; gs.table.push_back(tc); gs.turn = next_turn(gs); break; default: debug(CRITICAL, "Invalid movetype!"); } if (move.movetype != PICKUP) { remove_card(gs.players[move.player].hand, move.owncard); } if (move.movetype == HIT) { reset_let_go_over(gs); } } OverType can_go_over(const GameState &gs) { if (gs.time_to_go_over != 0 && time(NULL) >= gs.time_to_go_over) { return OVER_TIMEOUT; } bool let_go_over = false; std::map::const_iterator iter; for (iter = gs.players.begin(); iter != gs.players.end(); iter++) { if (iter->second.state == ACTIVE && gs.turn != iter->first) { if (player_can_add(iter->second, gs)) { if (iter->second.let_go_over) { let_go_over = true; } else { return NOT_OVER; } } } } if (let_go_over) { return OVER_LETGOOVER; } else { return OVER_CANTADD; } } void check_winners(GameState &gs) { if (gs.deck.size() != 0) { return; } std::map::iterator iter; for (iter = gs.players.begin(); iter != gs.players.end(); iter++) { if (iter->second.hand.size() == 0 && iter->second.state == ACTIVE) { gs.winners.push_back(iter->first); iter->second.state = OBSERVER; if (gs.turn == iter->first) { // Because we do this, check_winners _must_ be called // after any move-induced next-turn has been processed. gs.turn = next_turn(gs); } } } } void fill_hands(GameState &gs) { int need; std::map::iterator iter = gs.players.begin(); // Find the player in turn while (iter->first != gs.turn) { iter++; if (iter == gs.players.end()) { debug(CRITICAL, "Player in turn not found"); return; } } do { if (iter->second.state == ACTIVE) { need = gs.c_cards_in_hand - iter->second.hand.size(); while (need > 0 && gs.deck.size() != 0) { need -= 1; iter->second.hand.push_back(gs.deck.back()); gs.deck.pop_back(); } } iter++; if (iter == gs.players.end()) { iter = gs.players.begin(); } } while (iter->first != gs.turn); } // Check that atleast two players are in game bool game_ended(const GameState &gs) { int count = 0; std::map::const_iterator iter; for (iter = gs.players.begin(); iter != gs.players.end(); iter++) { if (iter->second.state == ACTIVE) { count += 1; } if (count >= 2) { return false; } } return true; } /* For all but pickup: - Player must have owncard in hand For pickup: - Must be players turn, table may not be empty For hits: - If player's turn, table must be empty - If not player's turn: - there must be cards of the same rank already on table - there may not be more unbeaten cards on table than at beaters hand For beats: - Must be players turn, table may not be empty - Card to beat must be on table - Must be a beating combination For moves: - Must be players turn, table may not be empty - Table must contain cards of only one rank - No cards on table have been beaten - No more cards than next player has */ bool legal_move(const GameState &gs, const Move &move) { std::map::const_iterator playeriter = gs.players.find(move.player); if (playeriter == gs.players.end()) { debug(INFO, "Invalid player"); return false; } const Player &player = playeriter->second; if (move.movetype != PICKUP && \ std::find(player.hand.begin(), player.hand.end(), move.owncard) == player.hand.end()) { // Own card not in hand debug(INFO, "Card not in hand"); return false; } switch (move.movetype) { case PICKUP: return (gs.turn == move.player) && gs.table.size() != 0; case HIT: if (gs.turn == move.player) { return gs.table.size() == 0; } else { return may_add_card(gs, move.owncard); } case BEAT: return (gs.turn == move.player) && \ (gs.table.size() != 0) && \ card_on_table(gs.table, move.othercard) && \ can_beat(move.othercard, move.owncard, gs.trump.suit); case MOVE: return (gs.turn == move.player) && \ (gs.table.size() != 0) && \ one_rank_only(gs.table, move.owncard.rank) && \ ((gs.table.size() + 1) <= \ gs.players.find(next_turn(gs))->second.hand.size()); default: debug(CRITICAL, "Invalid movetype!"); } return false; } bool may_add_card(const GameState &gs, const Card &card) { // Count of unbeaten cards int i; int count = 0; for (i = 0; i < gs.table.size(); i++) { if (! is_card(gs.table.at(i).beater)) { count += 1; } } if (count >= gs.players.find(gs.turn)->second.hand.size()) { // Too many if we add one return false; } if (!is_card(card) && is_joker(card)) { return true; } return rank_exists_on_table(gs.table, card); } int next_turn(const GameState &gs) { std::map::const_iterator iter = gs.players.begin(); // Find the player in turn while (iter->first != gs.turn) { iter++; } do { iter++; if (iter == gs.players.end()) { iter = gs.players.begin(); } if (iter->second.state == ACTIVE) { return iter->first; } } while (iter->first != gs.turn); debug(CRITICAL, "No next turn possible!"); return gs.turn; } bool player_can_add(const Player &player, const GameState &gs) { int i; for (i = 0; i < player.hand.size(); i++) { if (may_add_card(gs, player.hand.at(i))) { return true; } } return false; } void put_over(GameState &gs) { gs.table.clear(); fill_hands(gs); gs.time_to_go_over = 0; reset_let_go_over(gs); } void reset_let_go_over(GameState &gs) { std::map::iterator iter; for (iter = gs.players.begin(); iter != gs.players.end(); iter++) { iter->second.let_go_over = false; } } void start_game(GameState &gs) { std::map::iterator iter; for (iter = gs.players.begin(); iter != gs.players.end(); iter++) { if (iter->second.state == ACTIVE) { iter->second.hand.clear(); } } gs.table.clear(); gs.deck.clear(); gs.time_game_started = time(NULL); gs.time_to_go_over = 0; gs.winners.clear(); // Fill deck int i; for (i = 0; i < gs.c_deck_count; i++) { init_deck(gs.deck, (gs.c_allow_jokers != 0)); } do { shuffle_deck(gs.deck); } while (is_joker(gs.deck.front())); gs.trump = gs.deck.front(); // Random first player Random rng; int pos = rng.random_in_range(0, gs.players.size() - 1); gs.turn = gs.players.begin()->first; while (pos--) gs.turn = next_turn(gs); // Fill hands fill_hands(gs); }