基于终端的Golang纸牌游戏客户端与多人服务器(支持SSH对战)

基于终端的Golang纸牌游戏客户端与多人服务器(支持SSH对战) 基于终端的克里比奇纸牌客户端 crib[0] 的第一个版本现已发布。它可以连接到克里比奇服务器 jack[1]。计划在 https://cribbage.world 提供一个基于网页的客户端。

您需要使用 SSH 客户端来访问服务器。在 Windows 上,PuTTY 是一个流行的选择。

要开始游戏,请连接到 cribbage.world 的 22000 端口:

ssh cribbage.world -p 22000

连接成功后,如果没有其他人在等待游戏,您需要等待对手连接。对手连接后,使用键盘上的数字 1-6 来选择牌。

当游戏等待您确认某些信息时(例如“过”、“对手得了 # 分”等),请按空格键继续。

  1. https://gitlab.com/tslocum/crib
  2. https://gitlab.com/tslocum/jack

更多关于基于终端的Golang纸牌游戏客户端与多人服务器(支持SSH对战)的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于基于终端的Golang纸牌游戏客户端与多人服务器(支持SSH对战)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个非常有趣的Go语言项目!基于终端的克里比奇纸牌游戏客户端和服务器实现展示了Go在网络编程和并发处理方面的强大能力。让我来分析一下这个项目的技术实现:

项目架构分析

这个项目采用了经典的客户端-服务器架构:

// 服务器端大致结构示意
package main

import (
    "net"
    "golang.org/x/crypto/ssh"
)

type GameServer struct {
    sshConfig *ssh.ServerConfig
    games     map[string]*CribbageGame
    players   map[net.Conn]*Player
}

// SSH服务器监听
func (s *GameServer) Start() error {
    listener, err := net.Listen("tcp", ":22000")
    if err != nil {
        return err
    }
    
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        go s.handleConnection(conn)
    }
}

客户端实现要点

客户端需要处理SSH连接和终端UI:

// 客户端连接示例
package main

import (
    "fmt"
    "golang.org/x/crypto/ssh"
    "golang.org/x/term"
)

type CribbageClient struct {
    sshClient *ssh.Client
    session   *ssh.Session
    stdin     io.Writer
    stdout    io.Reader
}

func (c *CribbageClient) Connect(host string, port int) error {
    config := &ssh.ClientConfig{
        User: "guest",
        Auth: []ssh.AuthMethod{
            ssh.Password(""),
        },
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }
    
    addr := fmt.Sprintf("%s:%d", host, port)
    client, err := ssh.Dial("tcp", addr, config)
    if err != nil {
        return err
    }
    
    session, err := client.NewSession()
    if err != nil {
        return err
    }
    
    // 设置终端模式
    modes := ssh.TerminalModes{
        ssh.ECHO:          0,     // 禁用回显
        ssh.TTY_OP_ISPEED: 14400, // 输入速度
        ssh.TTY_OP_OSPEED: 14400, // 输出速度
    }
    
    if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
        return err
    }
    
    c.sshClient = client
    c.session = session
    return nil
}

游戏逻辑处理

克里比奇游戏的核心逻辑需要处理:

// 游戏状态管理
type CribbageGame struct {
    players [2]*Player
    deck    []Card
    crib    []Card
    turn    int
    scores  [2]int
}

// 处理玩家出牌
func (g *CribbageGame) PlayCard(playerIndex int, cardIndex int) error {
    player := g.players[playerIndex]
    if cardIndex < 0 || cardIndex >= len(player.hand) {
        return fmt.Errorf("invalid card index")
    }
    
    card := player.hand[cardIndex]
    // 验证出牌合法性
    if !g.isValidPlay(card) {
        return fmt.Errorf("invalid play")
    }
    
    // 从手牌移除
    player.hand = append(player.hand[:cardIndex], player.hand[cardIndex+1:]...)
    
    // 更新游戏状态
    g.updateScore(playerIndex, card)
    
    return nil
}

// 计分逻辑
func (g *CribbageGame) calculateScore(cards []Card) int {
    score := 0
    
    // 计算15s
    score += g.countFifteens(cards)
    
    // 计算对子
    score += g.countPairs(cards)
    
    // 计算顺子
    score += g.countRuns(cards)
    
    // 计算同花
    score += g.countFlush(cards)
    
    return score
}

并发处理

服务器需要处理多个并发连接:

// 游戏房间管理
type GameRoom struct {
    mu       sync.RWMutex
    waiting  *Player
    games    map[string]*CribbageGame
}

func (r *GameRoom) MatchPlayer(p *Player) *CribbageGame {
    r.mu.Lock()
    defer r.mu.Unlock()
    
    if r.waiting == nil {
        r.waiting = p
        return nil
    }
    
    // 创建新游戏
    game := NewCribbageGame(r.waiting, p)
    gameID := generateGameID()
    r.games[gameID] = game
    
    r.waiting = nil
    return game
}

终端UI交互

处理键盘输入和游戏显示:

// 终端输入处理
func (c *CribbageClient) HandleInput() {
    oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
    if err != nil {
        panic(err)
    }
    defer term.Restore(int(os.Stdin.Fd()), oldState)
    
    reader := bufio.NewReader(os.Stdin)
    for {
        char, _, err := reader.ReadRune()
        if err != nil {
            break
        }
        
        switch {
        case char >= '1' && char <= '6':
            // 选择牌
            cardIndex := int(char - '1')
            c.selectCard(cardIndex)
        case char == ' ':
            // 确认/继续
            c.confirm()
        case char == 'q':
            // 退出
            return
        }
    }
}

这个项目很好地展示了Go语言在构建网络游戏服务器方面的优势,特别是:

  1. 使用标准库的netcrypto/ssh包处理SSH连接
  2. 利用goroutine轻松处理并发连接
  3. 通过通道进行游戏状态同步
  4. 简洁的终端UI交互实现

项目的SSH对战功能特别值得注意,它提供了安全的通信通道,同时保持了低延迟的游戏体验。

回到顶部