feat: add core data model (Card, Deck, RuleConfig, Constants)

This commit is contained in:
xiaji
2026-05-29 09:06:49 +08:00
parent 2dea531138
commit 016098f213
4 changed files with 245 additions and 0 deletions

70
src/core/card.gd Normal file
View File

@@ -0,0 +1,70 @@
# src/core/card.gd
# Pure data — no Godot dependencies
class_name Card
extends RefCounted
const MIN_ID := 0
const MAX_ID := 107
var card_id: int
var original_id: int
var _suit: int
var _rank: int
static func create(original_id: int, suit: int, rank: int) -> Card:
var c := Card.new()
c.original_id = original_id
c._suit = suit
c._rank = rank
c.card_id = original_id
return c
static func make_full_deck_ids() -> Array[int]:
var ids: Array[int] = []
for i in range(108):
ids.append(i)
return ids
static func card_id_from_deck(original_id: int, deck_index: int) -> int:
return original_id + deck_index * 54
func suit() -> int:
return _suit
func rank() -> int:
return _rank
func card_value() -> int:
return _suit * 20 + _rank
func is_joker() -> bool:
return _rank == 15 or _rank == 16
func is_heart() -> bool:
return _suit == 1
func matches(other: Card) -> bool:
return card_value() == other.card_value()
func equals(other: Card) -> bool:
return card_id == other.card_id
func compare_to(other: Card) -> int:
var r := _rank - other._rank
if r != 0:
return r
return _suit - other._suit
func to_packed() -> int:
return (_suit << 8) | (_rank & 0xFF)
static func from_packed(packed: int) -> Card:
var suit := (packed >> 8) & 0xFF
var rank := packed & 0xFF
return Card.create(0, suit, rank)
func _to_string() -> String:
var suits := ["S", "H", "C", "D", "SJ", "BJ"]
var ranks := ["", "", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "SJ", "BJ"]
return "%s%s" % [suits[_suit], ranks[_rank]]

77
src/core/constants.gd Normal file
View File

@@ -0,0 +1,77 @@
# src/core/constants.gd
# Zero Godot dependency — no class_name, use static functions to avoid global state.
const RESULT_OK := 0
const ERR_INVALID_CARDS := 1
const ERR_NOT_YOUR_TURN := 2
const ERR_CANNOT_PASS := 3
const ERR_CARD_NOT_FOUND := 4
const ERR_HAND_EMPTY := 5
const ERR_INVALID_TRIBUTE := 6
const ERR_GAME_OVER := 7
const ERR_STATE_CONFLICT := 8
const SUIT_SPADE := 0
const SUIT_HEART := 1
const SUIT_CLUB := 2
const SUIT_DIAMOND := 3
const SUIT_JOKER_SMALL := 4
const SUIT_JOKER_BIG := 5
const RANK_2 := 2
const RANK_3 := 3
const RANK_4 := 4
const RANK_5 := 5
const RANK_6 := 6
const RANK_7 := 7
const RANK_8 := 8
const RANK_9 := 9
const RANK_10 := 10
const RANK_J := 11
const RANK_Q := 12
const RANK_K := 13
const RANK_A := 14
const RANK_SMALL_JOKER := 15
const RANK_BIG_JOKER := 16
const TYPE_SINGLE := 0
const TYPE_PAIR := 1
const TYPE_TRIPLE := 2
const TYPE_TRIPLE_PLUS_TWO := 3
const TYPE_STRAIGHT := 4
const TYPE_CONSECUTIVE_PAIRS := 5
const TYPE_STEEL_PLATE := 6
const TYPE_STRAIGHT_FLUSH := 7
const TYPE_BOMB := 8
const TYPE_ROCKET := 9
const TURN_PLAY := 0
const TURN_PASS := 1
const TURN_TRIBUTE_GIVE := 2
const TURN_TRIBUTE_RETURN := 3
const PHASE_INIT := 0
const PHASE_DEAL := 1
const PHASE_TRIBUTE := 2
const PHASE_PLAY := 3
const PHASE_LEVEL_UP := 4
const PHASE_GAME_OVER := 5
const WILD_SOURCE_GRADE := 0
const WILD_SOURCE_SYSTEM := 1
const MAX_AI_DECISION_MS := 3000
const BEAM_WIDTH := 50
const MAX_ENUM_NODES := 10000
const TOTAL_CARDS := 108
const CARDS_PER_PLAYER := 27
const PLAYER_COUNT := 4
static func make_result(ok: bool, error_code: int, data = null) -> Dictionary:
return {"ok": ok, "error_code": error_code, "data": data}
static func success(data = null) -> Dictionary:
return {"ok": true, "error_code": RESULT_OK, "data": data}
static func err(code: int) -> Dictionary:
return {"ok": false, "error_code": code, "data": null}

58
src/core/deck.gd Normal file
View File

@@ -0,0 +1,58 @@
# src/core/deck.gd
class_name Deck
extends RefCounted
var _cards: Array[Card] = []
static func _suit_for(original_id: int) -> int:
if original_id == 52:
return 4
if original_id == 53:
return 5
return original_id % 4
static func _rank_for(original_id: int) -> int:
if original_id == 52:
return 15
if original_id == 53:
return 16
return 2 + (original_id / 4)
static func create(seed: int = -1) -> Deck:
var d := Deck.new()
d._cards = []
for deck_idx in range(2):
for orig_id in range(54):
var global_id := Card.card_id_from_deck(orig_id, deck_idx)
var suit := _suit_for(orig_id)
var rank := _rank_for(orig_id)
var c := Card.create(orig_id, suit, rank)
c.card_id = global_id
d._cards.append(c)
if seed >= 0:
d._shuffle_with_seed(seed)
else:
d._shuffle_random()
return d
func _shuffle_with_seed(seed: int) -> void:
var rng := RandomNumberGenerator.new()
rng.seed = seed
for i in range(_cards.size() - 1, 0, -1):
var j := rng.randi_range(0, i)
var tmp := _cards[i]
_cards[i] = _cards[j]
_cards[j] = tmp
func _shuffle_random() -> void:
_shuffle_with_seed(Time.get_unix_time_from_system() as int)
func deal(count: int) -> Array[Card]:
var result: Array[Card] = []
var actual := mini(count, _cards.size())
for _i in range(actual):
result.append(_cards.pop_back())
return result
func remaining() -> int:
return _cards.size()

40
src/core/rule_config.gd Normal file
View File

@@ -0,0 +1,40 @@
# src/core/rule_config.gd
class_name RuleConfig
extends RefCounted
const BOMB_BY_RANK := 0
const BOMB_BY_COUNT := 1
const TEAM_FIXED_1_3 := 0
const TEAM_RANDOM := 1
var same_suit_straight_beats_bomb: bool = true
var double_down_levels: int = 3
var can_tribute_wild: bool = false
var tribute_return_max_rank: int = 10
var straight_extends_to_ace: bool = true
var bomb_compare_priority: int = BOMB_BY_RANK
var wild_count: int = 2
var team_formation: int = TEAM_FIXED_1_3
static func standard() -> RuleConfig:
var c := RuleConfig.new()
return c
static func huaian() -> RuleConfig:
var c := RuleConfig.new()
c.same_suit_straight_beats_bomb = false
c.straight_extends_to_ace = false
return c
func to_hash_data() -> Dictionary:
return {
"same_suit_bomb": same_suit_straight_beats_bomb,
"double_down": double_down_levels,
"can_tribute_wild": can_tribute_wild,
"tribute_return_max": tribute_return_max_rank,
"straight_ace": straight_extends_to_ace,
"bomb_compare": bomb_compare_priority,
"wild_count": wild_count,
"team": team_formation
}