![]() LivingRPS 界面樣例 | |
基本資料 | |
用語名稱 | LivingRPS |
---|---|
其他表述 | 賽博石頭剪子布、石頭剪子布版鬥蛐蛐 |
用語出處 | 生態領域學術研究 |
相關條目 | 電子鬥蛐蛐 |
LivingRPS是一種以石頭剪子布為主題的電子鬥蛐蛐遊戲,中外皆有流行。
RPS 即 Rock-Paper-Scissors 的英文縮寫。
由於石頭剪刀布之間循環剋制的特性,很容易據此構造出「三個陣營相互拉扯」的場景。
表現形式一般是在空白畫面上放置相等數量的石頭、剪刀和布(或者紙)三種貼圖(或emoji),然後所有的貼圖在畫面上隨機遊走。過程中,如果某個剪刀貼圖碰到某個布就會將該布的貼圖原地替換成剪刀貼圖,其餘情況同理。
在這種「循環捕食」的機制下,最後有可能出現:
上述這些情況觀賞性和寓意性兼具,而隨機要素也為其加入了懸念性,因此也會演化出「猜測誰最後一統天下」的玩法,催生出大量「省流」之類的彈幕。
這類動態系統以及其可視化最早可追溯到20世紀末和21世紀初的數學與生態學研究。石頭剪刀布是一種典型的循環競爭模型[1],在三次元生態中真實存在,但是有別於食物鏈這種單向的捕食競爭。
此後,一些有關動力學系統、生物競爭和數學建模的科普向教學視頻使用了這種石頭剪子布混戰的動畫進行可視化,用以解說講解,被認為是此種「石頭剪子布」電子鬥蛐蛐的首開先河者。
作為一個標準的動力學體系,遊戲的規則是,兩個不同的emoji碰到一起就會按照石頭剪子布規則將對方轉化。因此,所有emoji均為石頭/剪刀/布是整個體系唯三的穩定狀態(或稱吸收態)。在吸收態下,這樣的轉化不會繼續發生。因此,網友們看到的「曠日持久的拉鋸戰」只是過程性的暫態(換句話說,不穩定平衡),終究會走向終結,這也為視頻增加了懸鏈的成分。
使用 Python 的 pygame 就可以實現這樣的電子鬥蛐蛐程序。下面的程序執行後,即可顯示條目封面圖片的界面和 HUD。
import pygame
import random
import math
pygame.init()
W, H = 1000, 800
screen = pygame.display.set_mode((W, H))
clock = pygame.time.Clock()
pygame.display.set_caption("Rock–Paper–Scissors dynamics")
# 參數
NUM_AGENTS = 300
RADIUS = 12 # 碰撞半徑
SPEED = 3 # 速度
# 類型 & 貼圖路徑(你可以替換為本地 PNG)
TYPES = {
"rock": {"emoji": "🪨", "color": (200, 50, 50)},
"paper": {"emoji": "📄", "color": (50, 200, 50)},
"scissors": {"emoji": "✂️", "color": (50, 50, 200)}
}
# 剪刀石頭布邏輯
def beats(a, b):
return (a == "rock" and b == "scissors") or \
(a == "scissors" and b == "paper") or \
(a == "paper" and b == "rock")
# Agent 類
class Agent:
def __init__(self):
self.x = random.uniform(RADIUS, W - RADIUS)
self.y = random.uniform(RADIUS, H - RADIUS)
angle = random.uniform(0, 2 * math.pi)
self.vx = math.cos(angle) * SPEED
self.vy = math.sin(angle) * SPEED
self.type = random.choice(["rock", "paper", "scissors"])
def move(self):
self.x += self.vx
self.y += self.vy
# 邊界反彈
if self.x - RADIUS < 0:
self.x = RADIUS
self.vx *= -1
elif self.x + RADIUS > W:
self.x = W - RADIUS
self.vx *= -1
if self.y - RADIUS < 0:
self.y = RADIUS
self.vy *= -1
elif self.y + RADIUS > H:
self.y = H - RADIUS
self.vy *= -1
def draw(self, screen, font):
# 渲染 emoji(中心對齊)
emoji_surface = font.render(TYPES[self.type]["emoji"], True, (0, 0, 0))
rect = emoji_surface.get_rect(center=(int(self.x), int(self.y)))
screen.blit(emoji_surface, rect)
# 初始化粒子 & 字體
agents = [Agent() for _ in range(NUM_AGENTS)]
emoji_font = pygame.font.SysFont("Segoe UI Emoji", 24)
hud_font = pygame.font.SysFont("Sen", 20)
# 主循環
running = True
while running:
screen.fill((255, 255, 255))
# 移動 & 畫出粒子
for agent in agents:
agent.move()
agent.draw(screen, emoji_font)
# 粒子碰撞檢測
for i in range(len(agents)):
for j in range(i + 1, len(agents)):
a, b = agents[i], agents[j]
dx = b.x - a.x
dy = b.y - a.y
dist = math.hypot(dx, dy)
if dist < 2 * RADIUS and dist > 0:
# 類型轉換
if beats(a.type, b.type):
b.type = a.type
elif beats(b.type, a.type):
a.type = b.type
# 反彈處理
nx, ny = dx / dist, dy / dist
dvx = b.vx - a.vx
dvy = b.vy - a.vy
impact = dvx * nx + dvy * ny
if impact < 0:
a.vx += impact * nx
a.vy += impact * ny
b.vx -= impact * nx
b.vy -= impact * ny
# 推開重疊
overlap = 2 * RADIUS - dist
a.x -= nx * overlap / 2
a.y -= ny * overlap / 2
b.x += nx * overlap / 2
b.y += ny * overlap / 2
# 繪製 HUD(半透明背景 + 文字)
hud_surface = pygame.Surface((160, 90), pygame.SRCALPHA)
hud_surface.fill((240, 240, 240, 200)) # 最後一個值是透明度 (0~255)
# 統計數量
counts = {"rock": 0, "paper": 0, "scissors": 0}
for agent in agents:
counts[agent.type] += 1
# 繪製文字
lines = [
f"Rock: {counts['rock']}",
f"Scissors: {counts['scissors']}",
f"Paper: {counts['paper']}"
]
for idx, line in enumerate(lines):
text = hud_font.render(line, True, (0, 0, 0))
hud_surface.blit(text, (10, 10 + idx * 25))
screen.blit(hud_surface, (10, 10))
# 事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.display.flip()
clock.tick(60)
pygame.quit()
(待補充)