Git Product home page Git Product logo

simple-chess's Introduction

Creating a chess.com/lichess clone using Go and Vue

chess picture

Let's create a chess.com/lichess clone MVP using Go and Vue!

What are we going to build?

A simplified version of chess.com or lichess.org, that works like this:

  • You navigate to the website from your browser.
  • If there's already one player waiting for a second player to join his game, you join that game.
  • Otherwise, you create a new game and wait for a second player to join.

demo gif

Goals

It should be as simple as possible while being extensible.

It should be a valid starting point for building something bigger by iterating over it.

How are we going to build it?

We are going to build a backend using Go, a frontend using Vue and we are going to use WebSockets to communicate between them.

Why Go?

First of all, I think it makes sense to build this using Go because of its concurrency model and performance.

But there are alternatives like Elixir, Java, Kotlin, Rust... even Python or NodeJS.
So why Go instead?

Go has become one of my favorite languages because of its simplicity.
When you start coding in Go you miss a lot of features from other languages, because Go lacks some a lot of features by design.
Soon you realize that's good: It usually doesn't have the things you don't really need.

That's why Go addresses The Zen Of Python way better than Python itself.
Especially when it comes to the 13th principle: "There should be one-- and preferably only one --obvious way to do it."

The more features a language has, the more Analysis Paralysis you get.
Have a look at The Paradox of Choice if you are not convinced yet.

Why Vue?

I'm a backend engineer.

I tried with React first, as I feel it is kind of the defacto standard. But I failed: it was too complex for my liking.

Then I tried with Web Components, but it didn't work out as expected either.

Then I found Chessground and its Vue component wrapper vue3-chessboard.

Enough talking, let's start coding!

The idea is the following:

  • Client 1 creates a WebSocket connection and waits for the game to start.
  • Client 2 creates a WebSocket connection and waits for the game to start.
  • Once the server has those two clients connected, it emits a "start" event, telling each client which color are they playing.
  • Then it is white's turn, so white client makes a move and sends a "move" event to the server. The server forwards that event to the other client. Then black knows it is its turn and which move did white make.
  • The previous point repeats until the game ends, interchanging white and black roles.

Note

Notice that we are going to create the whole project just by editing 3 files and writing less than 200 lines of code.

The server part

Create a folder for the project and jump into it

❯ mkdir simple-chess && cd simple-chess

Create a folder for the backend and jump into it

❯ mkdir backend && cd backend

Start your go project (change your GitHub username accordingly)

❯ go mod init github.com/alvaronaschez/simple-chess

Install Gorilla WebSocket dependency

❯ go get github.com/gorilla/websocket

Create the main.go file

backend/main.go
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  2048,
	WriteBufferSize: 2048,
}

var game *ChessGame

func wsHandler(w http.ResponseWriter, r *http.Request) {
	upgrader.CheckOrigin = func(r *http.Request) bool { return true }
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
	}

	if game == nil {
		game = NewChessGame(ws)
	} else {
		game.Join(ws)
		game = nil
	}
}

func main() {
	fmt.Println("Listening at port 5555")
	http.HandleFunc("/ws", wsHandler)
	log.Fatal(http.ListenAndServe(":5555", nil))
}

Create the ChessGame class

backend/game.go
package main

import (
	"errors"

	"github.com/gorilla/websocket"
)

type ChessGame struct {
	whiteWebsocket *websocket.Conn
	blackWebsocket *websocket.Conn
}

type Message struct {
	Type      string `json:"type" validate:"required,oneof=start move error"`
	Color     string `json:"color" validate:"oneof=white black,required_if=Type start"`
	From      string `json:"from" validate:"required_if=Type move"`
	To        string `json:"to" validate:"required_if=Type move"`
	Promotion string `json:"promotion" validate:"oneof=q r b k,required_if=Type move"`
}

func NewChessGame(ws *websocket.Conn) *ChessGame {
	game := ChessGame{whiteWebsocket: ws}
	return &game
}

var ErrCannotJoinStartedGame = errors.New("cannot join a started game")

func (game *ChessGame) Join(ws *websocket.Conn) error {
	// you cannot join the same game twice
	if game.blackWebsocket != nil {
		return ErrCannotJoinStartedGame
	}
	game.blackWebsocket = ws
	whiteChannel := make(chan Message)
	blackChannel := make(chan Message)
	go playChess(game.whiteWebsocket, game.blackWebsocket, whiteChannel, blackChannel)
	go forwardFromWebsocketToChannel(game.whiteWebsocket, whiteChannel)
	go forwardFromWebsocketToChannel(game.blackWebsocket, blackChannel)
	return nil
}

func playChess(
	whiteWebsocket, blackWebsocket *websocket.Conn,
	whiteChannel, blackChannel <-chan Message,
) {
	turnWhite := true
	whiteWebsocket.WriteJSON(Message{Type: "start", Color: "white"})
	blackWebsocket.WriteJSON(Message{Type: "start", Color: "black"})
	for {
		select {
		case message := <-whiteChannel:
			if message.Type == "error" {
				return
			}
			if turnWhite {
				blackWebsocket.WriteJSON(message)
				turnWhite = false
			}
		case message := <-blackChannel:
			if message.Type == "error" {
				return
			}
			if !turnWhite {
				whiteWebsocket.WriteJSON(message)
				turnWhite = true
			}
		}
	}
}

func forwardFromWebsocketToChannel(ws *websocket.Conn, ch chan<- Message) {
	defer ws.Close()
	for {
		message := Message{}
		err := ws.ReadJSON(&message)

		if err != nil {
			ch <- Message{Type: "error"}
			return
		}

		ch <- message
	}
}

Run go mod tidy

❯ go mod tidy

The client part

Navigate to the project root folder

cd ..

Create a new Vue project (add TypeScript support if you want to use the code snippet as it is)

❯ npm create vue@latest

Vue.js - The Progressive JavaScript Framework

✔ Project name: … frontend
✔ Add TypeScript? … No / [Yes]
...

Jump into the newly created subproject

cd frontend

Install vue3-chessboard component

❯ npm i vue3-chessboard

Edit Vue project entry point frontend/src/App.vue

frontend/src/App.vue
<script setup lang="ts">
import { ref } from 'vue'
import { BoardApi, TheChessboard, type MoveEvent } from 'vue3-chessboard'
import 'vue3-chessboard/style.css'

let board: BoardApi
const color = ref()

const socket = new WebSocket('ws://localhost:5555/ws')
socket.addEventListener('message', (event) => {
  const message = JSON.parse(event.data)
  if (message.type === 'start') {
    color.value = message.color
  } else if (message.type === 'move') {
    const { from, to, promotion } = message
    board.move({ from, to, promotion })
  }
})

function handleBoardCreated(boardApi: BoardApi) {
  board = boardApi
}

function handleMove(move: MoveEvent) {
  if (!color.value.startsWith(move.color)) {
    return
  }
  const { from, to, promotion } = move
  const message = JSON.stringify({ from, to, promotion, color: color.value, type: 'move' })
  socket.send(message)
}
</script>

<template>
  <TheChessboard
    v-if="color"
    @move="handleMove"
    @board-created="handleBoardCreated"
    :player-color="color"
    :board-config="{ orientation: color }"
  />
  <h1 v-else>Waiting for player 2</h1>
</template>

Run the backend

❯ (cd backend && go run .)

Open another terminal and run the frontend

❯ (cd frontend && npm run dev)

Open two clients in the browser at http://localhost:5173/ and enjoy the game!

Thanks for reading!

simple-chess's People

Contributors

alvaronaschez avatar

Stargazers

 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.