单人扑克游戏:地城恶棍的Python实现(纯代码)
实现细节链接:传送门
import random as ra
import numpy as np
from tqdm import tqdm
import os
import re
import time
class PlayingCard(object):
def __init__(self) -> None:
"""
构建牌库[x,y]
获取公式
x = 54 // 4
y = 54 % 4
x \in [0,13],其中0~9代表数值1~10, 10/11/12代表J/Q/K, 13代表特殊牌(即王牌)
y \in [0,3],其中0代表方块,1代表梅花,2代表桃心,3代表黑桃
[13,0] 小王; [13,1] 大王
"""
self.library = np.linspace(1,54,54).astype(np.int8)
self.prefixs = ['\033[31m♦\033[0m','♣','\033[31m♥\033[0m','♠']
self.suffixs = [str(i) for i in range(1,11)] + ['J','Q','K']
self.specials = ["♚","\033[31m♚\033[0m"]
def card2index(self,index) -> tuple:
assert not int((index-27)/27), "[WARN] Card %d is illegal!" % index
index -= 1
x,y = index//4,index % 4
return (x,y)
def card2str(self,index) -> str:
assert not int((index-27)/27), "[WARN] Card %d is illegal!" % index
x,y = self.card2index(index)
return self.prefixs[y] + self.suffixs[x]
def shuffle(self,seed = None) -> np.array:
#洗牌,掩膜也会被同步洗牌
if seed is None:
seed = ra.randint(0,2^16)
ra.seed(seed)
ra.shuffle(self.library)
return self.library
def _inLibrary(self,index) -> bool:
#判断是否在卡里?
return np.where(self.library == index)[0].size > 0
def drop(self,indexList) -> np.array:
#丢弃某张牌
for index in indexList if type(indexList) == list else [indexList]:
assert self._inLibrary(index), "[WARN] Card %d is not in library!" % index
self.library = np.delete(self.library,
np.where(self.library == index))
return self.library
def reload(self) -> np.array:
#获得一副新牌
self.library = np.linspace(1,54,54).astype(np.int8)
return self.library
def getPrefix(self,index) -> int:
"""
得到扑克牌花色
其中0代表方块,1代表梅花,2代表桃心,3代表黑桃
"""
assert not int((index-27)/27), "[WARN] Card %d is illegal!" % index
return (index - 1) % 4
def getNumber(self,index) -> int:
#得到扑克牌数字
assert not int((index-27)/27), "[WARN] Card %d is illegal!" % index
return (index - 1) //4
def pop(self) -> int:
#取出牌库顶牌
if len(self) == 0:
return 0
pop = self.library[0]
self.library = self.library[1:]
return pop
def insert(self,index:int) -> np.array:
#在牌库底插入牌
assert not int((index-27)/27), "[WARN] Card %d is illegal!" % index
assert not self._inLibrary(index), "[WARN] Card %d is already in library!" % index
self.library = np.concatenate([self.library,np.array([index])])
return self.library
def __str__(self):
string = "Card Summary: %d\n" % len(self)
for x in range(13): #用于显示每一行(本来可以继续压缩的,但是怕可读性不行)
string += "\t".join([self.prefixs[y] + self.suffixs[x] for y in range(4)
if self._inLibrary(4 * x + y + 1)]) + "\n"
string += "\t".join([self.specials[i] for i in [0,1] if self._inLibrary(53 + i)])
return string
def __len__(self):
return len(self.library)
class Config(object):
def __init__(self,maxHealth) -> None:
self.maxHealth = maxHealth
self.health = maxHealth
self.weapon = 0
self.finalKill = 0
def configReload(self):
self.health = self.maxHealth
self.weapon = 0
self.finalKill = 0
print("[INFO] Player Health: %d/%d" % (self.health,self.maxHealth))
class Game(Config):
def __init__(self,maxHealth:int = 20,savePath = './checkpoint',seed = None) -> None:
super().__init__(maxHealth)
self.card = PlayingCard()
self.room = np.zeros(4,dtype = np.int8)
self._step = 0
self.actions = None
self._isSwitch = False #一个回合中只能刷新一次房间
self.playTime = time.strftime("%Y-%m-%d=%H-%M", time.localtime())
self.fileName = os.path.join(savePath,self.playTime)
if not os.path.exists(savePath):
os.mkdir(savePath)
with open(self.fileName,'w') as file: pass #创建文件夹
if seed is None:
seed = ra.randint(0,2^16)
self.reload(seed)
def reload(self,seed = None) -> None:
"""
该游戏不需要红色J,K,Q,A以及小王大王
可以设定随机种子来保证公平对战
"""
if seed is None:
seed = ra.randint(0,2^16)
self.configReload()
self.card.reload()
self.card.drop([(2 * i + 1) for i in [0,1,20,21,22,23,24,25]] + [53,54])
self.card.shuffle(seed)
print("[INFO] The game initialized successfully!")
def _isEnd(self) -> int:
"""
游戏结束机制:
1. 卡池只剩一张,返回1
2. 生命值少于0,返回-1
"""
if len(self.card) == 0: return 1
if self.health <= 0: return -1
return 0
def _updateRoom(self) -> None:
"""
更新地牢房间:
在牌库中抽牌,直至四张牌朝上形成一个房间
"""
for index in np.where(self.room == 0)[0]:
self.room[index] = self.card.pop()
def __len__(self) -> int:
return self._step
def __str__(self):
string = "Step:%d\t Left: %d" % (len(self),len(self.card)) + "\n" + "=" * 40
string += "\nRoom:\t" +"\t".join([self.card.card2str(index) for index in self.room])
string += "\n\n\nWeapon:\t%s" % (self.card.card2str(self.weapon) if self.weapon else "None")
string += " (" + self.card.card2str(self.finalKill) + ")" if self.finalKill else ""
string += "\nHealth:\t%d/%d" % (self.health,self.maxHealth)
return string
def _decode(self,action):
if not action: return -1
action = [int(index) for index in re.findall(r"\d",action)]
for index in action:
if index == 0:
if not self._isSwitch:
self.actions = 0
return 1
else:
print("[WARN] You can't switch room twice in one round!")
input("Press \"Enter\" key to continute")
return -1
if index not in [1,2,3,4]:
print("[WARN] Error action %d" % index)
input("Press \"Enter\" key to continute")
return -1
if len(action) != 3:
print("[WARN] The correct action length must be 3! Found %d" % len(action))
input("Press \"Enter\" key to continute")
return -1
self.actions = action
return 1
def _switchRoom(self) -> None:
self._isSwitch = True
for index in self.room:
self.card.insert(index)
self.room = np.zeros(4,dtype = np.int8)
def _action(self,action):
"""其中0代表方块,1代表梅花,2代表桃心,3代表黑桃"""
index = self.room[action - 1]
prefix = self.card.getPrefix(index)
suffix = self.card.getNumber(index) + 1
if suffix == 1: suffix = 14 #A代表14
if prefix % 2 == 1: #方块和黑桃代表怪物
print("[INFO] You meet the monster %s!" % self.card.card2str(index))
if self.weapon != 0:
damaged = suffix - self.card.getNumber(self.weapon
if self.finalKill == 0 else self.finalKill) - 1
else:
damaged = suffix
if damaged > 0:
self.health -= damaged #扣除血量
print("[INFO] Oh no! You lost %d health! (%d/%d)" %\
(damaged,self.health,self.maxHealth))
else:
self.finalKill = index #成功击杀怪物,但是你的武器会扣除伤害
print("[INFO] You successfully kill the monster!")
print("[INFO] But your weapon can only kill monsters below the value of %d" % suffix)
if prefix == 2: #桃心代表加血
self.health = min(self.maxHealth,self.health + suffix)
print("[INFO] You meet %s and restored %d health! (%d/%d)" %\
(self.card.card2str(index),suffix,self.health,self.maxHealth))
if prefix == 0: #方块代表武器
print("[INFO] You find the new weapon %s!" % self.card.card2str(index))
self.finalKill = 0
self.weapon = index #重新装配武器
def _getScore(self,index) -> int:
if index == 1:
index = self.card.pop()
prefix = self.card.getPrefix(index)
suffix = self.card.getNumber(index) + 1
if prefix == 2:
return self.health + suffix
else:
self._action(index)
return self.health
else:
score = self.health
index = self.card.pop()
while(index != 0):
prefix = self.card.getPrefix(index)
suffix = self.card.getNumber(index) + 1
score -= suffix if prefix %2 == 1 else 0 #减去所有怪物的伤害
index = self.card.pop()
for index in self.room:
if index == 0: continue
prefix = self.card.getPrefix(index)
suffix = self.card.getNumber(index) + 1
score -= suffix if prefix %2 == 1 else 0 #减去所有怪物的伤害
return score
def step(self) -> int:
result = self._isEnd() #判断游戏是否结束
if result != 0:
score = self._getScore(result)
print("[INFO] %s Score: %d" %\
("Victory!" if result == 1 else "Defeated.",score))
os.rename(self.fileName,self.fileName + "=Score%d" % score)
return 1
self._updateRoom()
ans = -1
while(ans == -1):
os.system("cls")
print(self)
time.sleep(0.2)
actions = input( "\n1-4: Choose card (e.g. \"1 2 4\") \n0: Switch room\n"+\
"\nPlease input the number to choose your actions:")
ans = self._decode(actions)
if self.actions == 0:
self._switchRoom()
print("[INFO] You switch the room!")
input("Press \"Enter\" key to continute")
return 0
for action in self.actions:
if self.health <= 0:
return 0
self._action(action)
self.room[action - 1] = 0
time.sleep(0.2)
self._step += 1
self._isSwitch = False
input("Press \"Enter\" key to continute")
return 0
if __name__ == "__main__":
game = Game()
try:
while(game.step() == 0):
pass
except KeyboardInterrupt:
os.system("cls") #清屏不给看