Recently a flurry of activity on the gaming front has resulted in development, deployment, and usage of the FujiNet Game Server system and a working client for Atari 8-bits. More client platforms are planned soon. The first working Game Server is an implementation of Five Card Stud. More games are also planned now that there is one amazing example.
Updates
Updated: July 3rd to point to the new official lobby.xex host on fujinet.online TNFS. The lobby game server itself is now also hosted on fujinet.online systems.
To get to the point- you can play right now- with an Atari 8-bit and your FujiNet- with other human players and a coven of bots if no humans are available.
We are informally coordinating communal game plays in our Discord, in the #developing-game-servers room. I try to play a bit every night around 10pm EDT.
How To Play Right Now
To play simply boot up CONFIG add a HOST slot (if you don’t already have it): fujinet.online
Navigate to the fujinet.online server and inside the ATARI folder is the lobby XEX- you should mount the _lobby.xex into the Drive 1 slot of your FujiNet.
Atari CONFIG
Now boot, continuing to hold OPTION to disable BASIC if your Atari has BASIC built-in.
You will be presented with the Lobby – this displays the attached Game Servers (more below). Select a room – the Lobby shows how many open human slots are available in each room. Some room have bots in them so you can always play a game no matter if human players are present or not.
Once you select a Room and then hold OPTION key the LOBBY will chain-load the 5cardstud.xex for you automatically.
Your Atari will boot up, connect to that server (each room is its own server instance) and be placed at the table. Now regular 5 card stud play continues. Hit ESC any time to get to the in-game menu where you can leave the table or quit the game. If you leave the game you will have a chance to now join another room without having to load up LOBBY.XEX.
5cardstud connect screen
ESC Menu Screen to leave
Table Join Screen to switch tables
Typical hand of poker with six bots and two humans (Thom and myself)
Interested? Hop on Discord and find out when someone is available to play.
What is Going on Here?
There are a few elements to the FujiNet Game System (FGS) – the Lobby, a Lobby Client, Game Servers and Game Clients.
The Idea
Three years ago Thomas CherryHomes had an idea about a centralized Game Server that all platforms could connect to using FujiNet over the Internet. It would have various Game available to connect to and play with human opponents. His skeleton code lives over at the server GitHub repo. Eventually enough people had FujiNets that some of them wanted to play together over the network and they started to collaborate on the Discord.
The Lobby Server
rogersm has lead the Lobby effort. He picked up on the idea that Thom CherryHomes had 3 years ago for a system of game servers and clients apps across the FujiNet platform ecosystem. Roger went off and implemented the Lobby Server portion of the FGS which is the critical hub and central location for discovery of any running Game Servers.
It’s written in Go and is located on the FujiNet GitHub in the servers repo. It defines and implements the LOBBY Spec for Game Servers to interact and register themselves with the lobby. The lobby spec is also used by the Lobby Clients to allow local discovery and then connecting to running servers to play.
Lobby Clients
The lobby clients are platform specific apps (currently just for Atari 8bit) that when launched connect to the Lobby and facilitate game server selection. The Lobby client is designed to handle future game servers without needing updates.
The Lobby Client for Atari 8-bit
Eric Carr has written an Atari 8-bit Lobby Client and its source is available on the GitHub fujinet-apps repo. The XEX is available on ec.tnfs.io to load and run right now.
Eric implemented user persistence using the FujiNet AppKey so that you will always be you after registering your name to the Lobby the first time you start. You need a SD card inserted into your FujiNet for the AppKey functionality. Eric wrote the lobby client in fastbasic.
fastbasic is the amazing modern BASIC project created by Daniel Serpell (dsmc) in 2017. It’s bytecode based compiler and can be cross-compiled from a modern PC. More info on fastbasic is available on the Atariwiki and Daniel’s GitHub repo.
Game Servers
Eric Carr continued his programming efforts and created the first Game Server- an implementation of 5 card stud poker. It’s up in the FujiNet servers repo in GitHub. It’s also written in Go. It implements itself as a number of server instances each with it’s own mix of bots or no bots at all. Eric’s game server is very complex and handles not only bots, the 5 card stud rules for the game experience but also entering and leaving tables, time-outs for players not responding and allowing errant FujiNet clients to reconnect – most times seamlessly – to existing in-play hands. It’s a remarkable server with tons of features. And apparently Eric was only getting started…
Game Clients
The client application loads on your 8-bit platform of choice (at this point in time it’s just Atari 8-bit) and using FujiNet connects to a Game Server. You would have already had to have loaded the a Lobby client to pick a game server. Eric Carr has created a fully featured 5 card stud client for Atari 8bit computers again using fastbasic. And it’s simply amazing.
Eric’s code is up on GitHub in the fujinet-apps repo. This is an amazing example of a full-featured fastbasic app that uses custom fonts, DLI and has very detailed fit and finish. The response of the interface is snappy and the flow and game play, while dictated for the most part by the rules of 5 card stud, is just easy and nice to play on the Atari.
Waiting for a Bot to decide, but I’ve won this round with my pair
The clients are for the most part dumb, all gameplay logic happens on the server and the app is in charge of interpreting the status send in JSON from the server though the FujiNet’s JSON parser and updating the clients screen, handing input (which is just cursor keys and ENTER, ESC) and polling the server for updates. It works amazing well as various wifi issues can cause momentary drops but the client can reconnect and resync up very nicely to an in-progress game.
Below is some captured output from the FujiNet device as it polls and receives a round of data from the Game server.
What is Next?
This first implementation of the FGS is a whole other level of tight, collaborative, and fun interaction that enables these older 8-bit platforms to participate in modern, networked, cross-client, gaming scenarios. There are plans for a C64, Vic20 and eventual Apple2, and ADAM clients for this initial game as soon as possible. If you are ready to help and want to see this on a platform then consider joining the Discord and helping us code them up!
Sample FujiNet Output
Here is output of the FujiNet showing a round of data from the Game Server to the Client. This is an end-of-hand message as you can see that I won the hand (“lastResult”:" ANDYEMU won by default).
21:04:47.078710 > ACK! 21:04:47.078719 > mgHttpClient::GET 21:04:47.078723 > 00143441 _perform 21:04:47.266835 > mgHttpClient: Host name resolved 21:04:47.287141 > mgHttpClient: Connected 21:04:47.333274 > mgHttpClient: Data written 21:04:47.397333 > mgHttpClient: HTTP response 21:04:47.397354 > Status: 200 21:04:47.397358 > Received: 882 21:04:47.397361 > Body: 361 bytes 21:04:47.397368 > buffer malloc(361) 21:04:47.397386 > mgHttpClient: Data received 21:04:47.397390 > mgHttpClient: Connection closed 21:04:47.397550 > 00143580 _perform status = 200, length = 361, chunked = 0 21:04:47.397559 > NetworkProtocolFS::read_file(361) 21:04:47.397563 > NetworkProtocolHTTP::read_file_handle(0x12b904f90,361) 21:04:47.397567 > NetworkProtocolHTTP::read_file_handle_data() 21:04:47.397572 > NetworkProtocol::read(361) 21:04:47.397577 > S: {"lastResult":" ANDYEMU won by default","round":1,"pot":3,"activePlayer":2,"moveTime":0,"viewing":0,"validMoves":null,"players":[{"name":" ANDYEMU","status":1,"bet":5,"move":"BET","purse":326,"hand":"8H2C"},{"name":"Clyd BOT","status":1,"bet":5,"move":"CALL","purse":313,"hand":"??5S"},{"name":"Kirk BOT","status":1,"bet":0,"move":"","purse":60,"hand":"??2D"}]} 21:04:47.397622 > Parsed JSON: { "lastResult": " ANDYEMU won by default", "round": 1, "pot": 3, "activePlayer": 2, "moveTime": 0, "viewing": 0, "validMoves": null, "players": [{ "name": " ANDYEMU", "status": 1, "bet": 5, "move": "BET", "purse": 326, "hand": "8H2C" }, { "name": "Clyd BOT", "status": 1, "bet": 5, "move": "CALL", "purse": 313, "hand": "??5S" }, { "name": "Kirk BOT", "status": 1, "bet": 0, "move": "", "purse": 60, "hand": "??2D" }] } 21:04:47.398008 > COMPLETE! 21:04:47.398020 > SIO CMD processed in 322 ms 21:04:47.398025 > -+ 21:04:47.427923 > 21:04:47.429228 > CF: 78 51 0c 00 d5 21:04:47.429240 > sioNetwork::sio_process 0x51 'Q': 0x0c, 0x00 21:04:47.429245 > inq_dstats = 128 21:04:47.429249 > ACK+! 21:04:47.429253 > <-SIO read 256 bytes 21:04:47.564837 > ACK! 21:04:47.564863 > S: [cJSON_IsString] ANDYEMU won by default 21:04:47.564876 > S: [cJSON_IsNumber] 1.000000 21:04:47.564886 > S: [cJSON_IsNumber] 3.000000 21:04:47.564892 > S: [cJSON_IsNumber] 2.000000 21:04:47.564896 > S: [cJSON_IsNumber] 0.000000 21:04:47.564902 > S: [cJSON_IsNumber] 0.000000 21:04:47.564907 > S: [cJSON_IsNull] 21:04:47.564913 > S: [cJSON_IsString] ANDYEMU 21:04:47.564917 > S: [cJSON_IsNumber] 1.000000 21:04:47.564922 > S: [cJSON_IsNumber] 5.000000 21:04:47.564926 > S: [cJSON_IsString] BET 21:04:47.564931 > S: [cJSON_IsNumber] 326.000000 21:04:47.564936 > S: [cJSON_IsString] 8H2C 21:04:47.564941 > S: [cJSON_IsString] Clyd BOT 21:04:47.564946 > S: [cJSON_IsNumber] 1.000000 21:04:47.564950 > S: [cJSON_IsNumber] 5.000000 21:04:47.564955 > S: [cJSON_IsString] CALL 21:04:47.564959 > S: [cJSON_IsNumber] 313.000000 21:04:47.564963 > S: [cJSON_IsString] ??5S 21:04:47.564968 > S: [cJSON_IsString] Kirk BOT 21:04:47.564973 > S: [cJSON_IsNumber] 1.000000 21:04:47.564977 > S: [cJSON_IsNumber] 0.000000 21:04:47.564982 > S: [cJSON_IsString] 21:04:47.564986 > S: [cJSON_IsNumber] 60.000000 21:04:47.564991 > S: [cJSON_IsString] ??2D 21:04:47.564998 > S: [cJSON_IsString] ANDYEMU won by default 21:04:47.565004 > S: [cJSON_IsNumber] 1.000000 21:04:47.565008 > S: [cJSON_IsNumber] 3.000000 21:04:47.565012 > S: [cJSON_IsNumber] 2.000000 21:04:47.565017 > S: [cJSON_IsNumber] 0.000000 21:04:47.565021 > S: [cJSON_IsNumber] 0.000000 21:04:47.565025 > S: [cJSON_IsNull] 21:04:47.565029 > S: [cJSON_IsString] ANDYEMU 21:04:47.565033 > S: [cJSON_IsNumber] 1.000000 21:04:47.565038 > S: [cJSON_IsNumber] 5.000000 21:04:47.565042 > S: [cJSON_IsString] BET 21:04:47.565046 > S: [cJSON_IsNumber] 326.000000 21:04:47.565051 > S: [cJSON_IsString] 8H2C 21:04:47.565055 > S: [cJSON_IsString] Clyd BOT 21:04:47.565059 > S: [cJSON_IsNumber] 1.000000 21:04:47.565064 > S: [cJSON_IsNumber] 5.000000 21:04:47.565068 > S: [cJSON_IsString] CALL 21:04:47.565072 > S: [cJSON_IsNumber] 313.000000 21:04:47.565076 > S: [cJSON_IsString] ??5S 21:04:47.565081 > S: [cJSON_IsString] Kirk BOT 21:04:47.565085 > S: [cJSON_IsNumber] 1.000000 21:04:47.565090 > S: [cJSON_IsNumber] 0.000000 21:04:47.565094 > S: [cJSON_IsString] 21:04:47.565098 > S: [cJSON_IsNumber] 60.000000 21:04:47.565102 > S: [cJSON_IsString] ??2D 21:04:47.565108 > S: [cJSON_IsString] ANDYEMU won by default 21:04:47.565113 > S: [cJSON_IsNumber] 1.000000 21:04:47.565118 > S: [cJSON_IsNumber] 3.000000 21:04:47.565122 > S: [cJSON_IsNumber] 2.000000 21:04:47.565127 > S: [cJSON_IsNumber] 0.000000 21:04:47.565131 > S: [cJSON_IsNumber] 0.000000 21:04:47.565135 > S: [cJSON_IsNull] 21:04:47.565139 > S: [cJSON_IsString] ANDYEMU 21:04:47.565143 > S: [cJSON_IsNumber] 1.000000 21:04:47.565148 > S: [cJSON_IsNumber] 5.000000 21:04:47.565152 > S: [cJSON_IsString] BET 21:04:47.565157 > S: [cJSON_IsNumber] 326.000000 21:04:47.565161 > S: [cJSON_IsString] 8H2C 21:04:47.565166 > S: [cJSON_IsString] Clyd BOT 21:04:47.565170 > S: [cJSON_IsNumber] 1.000000 21:04:47.565175 > S: [cJSON_IsNumber] 5.000000 21:04:47.565179 > S: [cJSON_IsString] CALL 21:04:47.565184 > S: [cJSON_IsNumber] 313.000000 21:04:47.565188 > S: [cJSON_IsString] ??5S 21:04:47.565193 > S: [cJSON_IsString] Kirk BOT 21:04:47.565197 > S: [cJSON_IsNumber] 1.000000 21:04:47.565202 > S: [cJSON_IsNumber] 0.000000 21:04:47.565206 > S: [cJSON_IsString] 21:04:47.565210 > S: [cJSON_IsNumber] 60.000000 21:04:47.565214 > S: [cJSON_IsString] ??2D 21:04:47.565221 > Query set to 21:04:47.565553 > COMPLETE! 21:04:47.565562 > SIO CMD processed in 138 ms 21:04:47.565568 > - 21:04:47.578607 > 21:04:47.579977 > CF: 78 53 0c 00 d7 21:04:47.579990 > sioNetwork::sio_process 0x53 'S': 0x0c, 0x00 21:04:47.580327 > ACK! 21:04:47.580334 > sioNetwork::sio_status_channel(1) 21:04:47.580338 > sio_status_channel() - BW: 280 C: 1 E: 1 21:04:47.580342 > ->SIO write 4 bytes 21:04:47.580675 > COMPLETE! 21:04:47.580693 > SIO CMD processed in 2 ms 21:04:47.585758 > + 21:04:47.594415 > 21:04:47.595686 > CF: 78 52 18 01 e3 21:04:47.595698 > sioNetwork::sio_process 0x52 'R': 0x18, 0x01 21:04:47.595703 > sioNetwork::sio_read( 280 bytes) 21:04:47.596036 > ACK! 21:04:47.596044 > ->SIO write 280 bytes 21:04:47.596376 > COMPLETE! 21:04:47.596397 > SIO CMD processed in 2 ms 21:04:47.605262 > -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The tnfs site address has changed from ec.tnfs.io to fujinet.online and the executible is now _lobby.exe in the ATARI folder.
Hello, I would like to ask if I could implement this on my TNFS server. Stay tuned.