การเขียน Multiplayer Text Adventure Engine ใน Node.js: การสร้าง Terminal Client (ตอนที่ 3)

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ ส่วนที่สามของซีรีส์นี้จะเน้นที่การเพิ่มไคลเอนต์แบบข้อความสำหรับเอ็นจิ้นเกมที่สร้างขึ้นในส่วนที่ 2 Fernando Doglio อธิบายการออกแบบสถาปัตยกรรมพื้นฐาน การเลือกเครื่องมือ และไฮไลท์ของโค้ดโดยแสดงวิธีสร้างข้อความ UI พื้นฐานด้วยความช่วยเหลือของ Node.js

ครั้งแรกที่ฉันแสดงให้คุณเห็นถึงวิธีการกำหนดโปรเจ็กต์เช่นนี้ และให้พื้นฐานของสถาปัตยกรรมรวมถึงกลไกที่อยู่เบื้องหลังเอ็นจิ้นเกมแก่คุณ จากนั้น ฉันแสดงให้คุณเห็นถึงการใช้งานพื้นฐานของเอ็นจิ้น — REST API พื้นฐานที่ให้คุณสำรวจโลกที่กำหนดโดย JSON

วันนี้ผมจะแสดงให้คุณเห็นถึงวิธีการสร้างไคลเอ็นต์ข้อความแบบเก่าสำหรับ API ของเราโดยใช้ Node.js

ส่วนอื่นๆ ของซีรีส์นี้

  • ตอนที่ 1: บทนำ
  • ส่วนที่ 2: การออกแบบเซิร์ฟเวอร์ Game Engine
  • ตอนที่ 4: การเพิ่มแชทในเกมของเรา

ทบทวนการออกแบบดั้งเดิม

เมื่อฉันเสนอโครงร่างพื้นฐานสำหรับ UI ครั้งแรก ฉันได้เสนอสี่ส่วนบนหน้าจอ:

(ตัวอย่างขนาดใหญ่)

แม้ว่าในทางทฤษฎีจะดูถูกต้อง แต่ฉันก็พลาดความจริงที่ว่าการสลับไปมาระหว่างการส่งคำสั่งเกมและข้อความตัวอักษรจะเป็นเรื่องที่ยุ่งยาก ดังนั้นแทนที่จะให้ผู้เล่นเปลี่ยนโหมดด้วยตนเอง เราจะให้ตัวแยกวิเคราะห์คำสั่งทำให้แน่ใจว่าสามารถแยกแยะว่าเรา กำลังพยายามสื่อสารกับเกมหรือเพื่อนของเรา

ดังนั้น แทนที่จะมีสี่ส่วนในหน้าจอของเรา ตอนนี้เราจะมีสามส่วน:

(ตัวอย่างขนาดใหญ่)

นั่นคือภาพหน้าจอจริงของไคลเอนต์เกมสุดท้าย คุณจะเห็นหน้าจอเกมทางด้านซ้าย และแชททางด้านขวา โดยมีกล่องอินพุตทั่วไปเพียงกล่องเดียวที่ด้านล่าง โมดูลที่เราใช้ช่วยให้เราปรับแต่งสีและเอฟเฟกต์พื้นฐานบางอย่างได้ คุณจะสามารถโคลนโค้ดนี้จาก Github และทำสิ่งที่คุณต้องการด้วยรูปลักษณ์

ข้อแม้หนึ่งข้อแม้ว่า: แม้ว่าภาพหน้าจอด้านบนจะแสดงการแชทที่ทำงานเป็นส่วนหนึ่งของแอปพลิเคชัน แต่เราจะให้ความสำคัญกับบทความนี้โดยเน้นที่การตั้งค่าโครงการและกำหนดกรอบงานที่เราสามารถสร้างแอปพลิเคชันที่ใช้ข้อความ UI แบบไดนามิกได้ เราจะเน้นที่การเพิ่มการสนับสนุนทางแชทในบทต่อไปและตอนสุดท้ายของซีรีส์นี้

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

เครื่องมือที่เราต้องการ

แม้ว่าจะมีไลบรารี่มากมายที่ให้เราสร้างเครื่องมือ CLI ด้วย Node.js การเพิ่ม UI แบบข้อความเป็นสัตว์ร้ายที่แตกต่างไปจากเดิมอย่างสิ้นเชิง โดยเฉพาะอย่างยิ่ง ฉันสามารถหาห้องสมุดได้เพียงแห่งเดียว

ไลบรารีนี้มีประสิทธิภาพมาก และมีคุณลักษณะมากมายที่เราจะไม่ใช้สำหรับโปรเจ็กต์นี้ (เช่น การแสดงเงา การลากและวาง และอื่นๆ) โดยพื้นฐานแล้วจะปรับใช้ไลบรารี ncurses ทั้งหมดอีกครั้ง (ไลบรารี C ซึ่งอนุญาตให้นักพัฒนาสร้าง UI แบบข้อความ) ซึ่งไม่มีการโยง Node.js และทำใน JavaScript โดยตรง ดังนั้น หากจำเป็น เราสามารถตรวจสอบรหัสภายในของมันได้เป็นอย่างดี (ซึ่งผมไม่แนะนำเว้นแต่คุณจะต้องทำจริงๆ)

แม้ว่าเอกสารประกอบของ Blessed จะค่อนข้างกว้างขวาง แต่ส่วนใหญ่ประกอบด้วยรายละเอียดส่วนบุคคลเกี่ยวกับแต่ละวิธีที่จัดเตรียมไว้ (แทนที่จะมีแบบฝึกหัดอธิบายวิธีใช้วิธีการเหล่านั้นร่วมกันจริง ๆ ) และขาดตัวอย่างทุกที่จึงอาจเจาะลึกได้ยาก หากคุณต้องเข้าใจวิธีการทำงานเฉพาะ เมื่อคุณเข้าใจสิ่งนี้แล้ว ทุกอย่างทำงานในลักษณะเดียวกัน ซึ่งเป็นข้อดีอย่างมาก เนื่องจากไม่ใช่ทุกไลบรารีหรือแม้แต่ภาษา (ฉันกำลังดูคุณอยู่ PHP) มีไวยากรณ์ที่สอดคล้องกัน

แต่เอกสารกัน; ข้อดีที่สำคัญสำหรับไลบรารีนี้คือใช้งานได้ตามตัวเลือก JSON ตัวอย่างเช่น หากคุณต้องการวาดกล่องที่มุมบนขวาของหน้าจอ ให้ทำดังนี้:

 var box = blessed.box({ top: '0', right: '0', width: '50%', height: '50%', content: 'Hello {bold}world{/bold}!', tags: true, border: { type: 'line' }, style: { fg: 'white', bg: 'magenta', border: { fg: '#f0f0f0' }, hover: { bg: 'green' } } });

อย่างที่คุณจินตนาการได้ แง่มุมอื่นๆ ของกล่องก็ถูกกำหนดไว้ด้วยเช่นกัน (เช่น ขนาดของกล่อง) ซึ่งสามารถปรับเปลี่ยนไดนามิกได้อย่างสมบูรณ์แบบตามขนาดของเทอร์มินัล ประเภทของเส้นขอบ และสี แม้กระทั่งสำหรับเหตุการณ์โฮเวอร์ หากคุณได้ทำการพัฒนา front-end มาก่อนแล้ว คุณจะพบว่าทั้งสองมีความเหลื่อมล้ำกันมาก

จุดที่ฉันพยายามทำที่นี่คือทุกอย่างที่เกี่ยวกับการแสดงกล่องได้รับการกำหนดค่าผ่านวัตถุ JSON ที่ส่งผ่านไปยังวิธีการของ box สำหรับฉันแล้ว นั่นสมบูรณ์แบบเพราะฉันสามารถแยกเนื้อหานั้นลงในไฟล์การกำหนดค่า และสร้างตรรกะทางธุรกิจที่สามารถอ่านและตัดสินใจว่าจะวาดองค์ประกอบใดบนหน้าจอ ที่สำคัญที่สุด มันจะช่วยให้เราเห็นว่าพวกมันจะหน้าตาเป็นอย่างไรเมื่อวาดเสร็จแล้ว

นี่จะเป็นพื้นฐานสำหรับด้าน UI ทั้งหมดของโมดูลนี้ ( เพิ่มเติมในวินาทีนั้น! )

สถาปัตยกรรมของโมดูล

สถาปัตยกรรมหลักของโมดูลนี้อาศัยวิดเจ็ต UI ทั้งหมดที่เราจะแสดง กลุ่มของวิดเจ็ตเหล่านี้ถือเป็นหน้าจอ และหน้าจอทั้งหมดเหล่านี้ถูกกำหนดไว้ในไฟล์ JSON ไฟล์เดียว (ซึ่งคุณสามารถพบได้ในโฟลเดอร์ /config )

ไฟล์นี้มีมากกว่า 250 บรรทัด การแสดงที่นี่ไม่สมเหตุสมผล คุณสามารถดูไฟล์แบบเต็มได้ทางออนไลน์ แต่ตัวอย่างข้อมูลขนาดเล็กจะมีลักษณะดังนี้:

 "screens": { "main-options": { "file": "./main-options.js", "elements": { "username-request": { "type": "input-prompt", "params": { "position": { "top": "0%", "left": "0%", "width": "100%", "height": "25%" }, "content": "Input your username: ", "inputOnFocus": true, "border": { "type": "line" }, "style": { "fg": "white", "bg": "blue", "border": { "fg": "#f0f0f0" }, "hover": { "bg": "green" } } } }, "options": { "type": "window", "params": { "position": { "top": "25%", "left": "0%", "width": "100%", "height": "50%" }, "content": "Please select an option: \n1. Join an existing game.\n2. Create a new game", "border": { "type": "line" }, "style": { //... } } }, "input": { "type": "input", "handlerPath": "../lib/main-options-handler", //... } } }

องค์ประกอบ "หน้าจอ" จะมีรายการหน้าจอภายในแอปพลิเคชัน แต่ละหน้าจอมีรายการวิดเจ็ต (ซึ่งฉันจะอธิบายคร่าวๆ) และทุกวิดเจ็ตมีคำจำกัดความเฉพาะของพรและไฟล์ตัวจัดการที่เกี่ยวข้อง (ถ้ามี)

คุณสามารถดูได้ว่าองค์ประกอบ "params" ทุกรายการ (ภายในวิดเจ็ตเฉพาะ) แสดงถึงชุดพารามิเตอร์จริงที่คาดไว้โดยวิธีที่เราเห็นก่อนหน้านี้อย่างไร คีย์ที่เหลือกำหนดไว้ที่นั่นช่วยให้บริบทเกี่ยวกับประเภทของวิดเจ็ตที่จะแสดงผลและพฤติกรรมของวิดเจ็ต

จุดที่น่าสนใจบางประการ:

ตัวจัดการหน้าจอ

ทุกองค์ประกอบหน้าจอมีคุณสมบัติไฟล์ที่อ้างอิงรหัสที่เกี่ยวข้องกับหน้าจอนั้น รหัสนี้ไม่มีอะไรนอกจากวัตถุที่ต้องมีวิธีการ init ต้น (ตรรกะการเริ่มต้นสำหรับหน้าจอนั้น ๆ จะเกิดขึ้นภายในนั้น) โดยเฉพาะเอ็นจิ้น UI หลักจะเรียกเมธอด init ของทุกหน้าจอ ซึ่งในทางกลับกัน ควรรับผิดชอบในการเริ่มต้นตรรกะที่อาจต้องการ (เช่น การตั้งค่าเหตุการณ์ของกล่องอินพุต)

ต่อไปนี้เป็นรหัสสำหรับหน้าจอหลัก ซึ่งแอปพลิเคชันขอให้ผู้เล่นเลือกตัวเลือกในการเริ่มเกมใหม่หรือเข้าร่วมเกมที่มีอยู่:

 const logger = require("../utils/logger") module.exports = { init: function(elements, UI) { this.elements = elements this.UI = UI this. this.setInput() }, moveToIDRequest: function(handler) { return this.UI.loadScreen('id-requests', (err, ) => { }) }, createNewGame: function(handler) { handler.createNewGame(this.UI.gamestate.APIKEY, (err, gameData) => { this.UI.gamestate.gameID = gameData._id handler.joinGame(this.UI.gamestate, (err) => { return this.UI.loadScreen('main-ui', { flashmessage: "You've joined game " + this.UI.gamestate.gameID + " successfully" }, (err, ) => { }) }) }) }, setInput: function() { let handler = require(this.elements["input"].meta.handlerPath) let input = this.elements["input"].obj let usernameRequest = this.elements['username-request'].obj let usernameRequestMeta = this.elements['username-request'].meta let question = usernameRequestMeta.params.content.trim() usernameRequest.setValue(question) this.UI.renderScreen() let validOptions = { 1: this.moveToIDRequest.bind(this), 2: this.createNewGame.bind(this) } usernameRequest.on('submit', (username) => { logger.info("Username:" +username) logger.info("Playername: " + username.replace(question, '')) this.UI.gamestate.playername = username.replace(question, '') input.focus() input.on('submit', (data) => { let command = input.getValue() if(!validOptions[+command]) { this.UI.setUpAlert("Invalid option: " + command) return this.UI.renderScreen() } return validOptions[+command](handler) }) }) return input } }

อย่างที่คุณเห็น วิธี init เรียกเมธอด setupInput ซึ่งโดยทั่วไปแล้วจะกำหนดค่าการเรียกกลับที่ถูกต้องเพื่อจัดการกับอินพุตของผู้ใช้ การโทรกลับนั้นถือตรรกะในการตัดสินใจว่าจะทำอย่างไรตามอินพุตของผู้ใช้ (ทั้ง 1 หรือ 2)

ตัวจัดการวิดเจ็ต

วิดเจ็ตบางตัว (โดยปกติคือวิดเจ็ตอินพุต) มีคุณสมบัติ handlerPath ซึ่งอ้างอิงไฟล์ที่มีตรรกะเบื้องหลังคอมโพเนนต์นั้น ซึ่งไม่เหมือนกับตัวจัดการหน้าจอก่อนหน้า สิ่งเหล่านี้ไม่สนใจองค์ประกอบ UI มากนัก แต่จะจัดการกับตรรกะของกาวระหว่าง UI และไลบรารีใดก็ตามที่เราใช้เพื่อโต้ตอบกับบริการภายนอก (เช่น API ของเอ็นจิ้นเกม)

ประเภทวิดเจ็ต

เพิ่มเติมเล็กน้อยในคำจำกัดความ JSON ของวิดเจ็ตคือประเภท แทนที่จะใช้ชื่อที่ Blessed กำหนดไว้สำหรับพวกเขา ฉันกำลังสร้างชื่อใหม่เพื่อให้ฉันมีที่ว่างมากขึ้นสำหรับพฤติกรรมของพวกเขา ท้ายที่สุด วิดเจ็ตหน้าต่างอาจไม่ได้ “แสดงเพียงข้อมูล” เสมอไป หรือช่องป้อนข้อมูลอาจไม่ทำงานในลักษณะเดียวกันเสมอไป

ส่วนใหญ่เป็นการย้ายที่ยึดไว้เพียงเพื่อให้แน่ใจว่าฉันมีความสามารถนั้นหากฉันต้องการมันในอนาคต แต่อย่างที่คุณเห็น ฉันไม่ได้ใช้ส่วนประกอบประเภทต่างๆ มากมาย

หลายหน้าจอ

แม้ว่าหน้าจอหลักจะเป็นหน้าจอที่ฉันแสดงให้คุณเห็นในภาพหน้าจอด้านบน แต่เกมต้องการหน้าจออื่นๆ สองสามหน้าจอเพื่อขอสิ่งต่างๆ เช่น ชื่อผู้เล่นของคุณ หรือไม่ว่าคุณจะสร้างเซสชันเกมใหม่หรือเข้าร่วมในหน้าจอที่มีอยู่ วิธีที่ฉันจัดการนั่นคือ อีกครั้ง ผ่านคำจำกัดความของหน้าจอทั้งหมดเหล่านี้ในไฟล์ JSON เดียวกัน และเพื่อย้ายจากหน้าจอหนึ่งไปยังหน้าจอถัดไป เราใช้ตรรกะภายในไฟล์ตัวจัดการหน้าจอ

เราสามารถทำได้ง่ายๆ โดยใช้รหัสบรรทัดต่อไปนี้:

 this.UI.loadScreen('main-ui', (err ) => { if(err) this.UI.setUpAlert(err) })

ฉันจะแสดงรายละเอียดเพิ่มเติมเกี่ยวกับคุณสมบัติ UI ในไม่กี่วินาที แต่ฉันแค่ใช้วิธี loadScreen นั้นเพื่อแสดงผลหน้าจออีกครั้งและเลือกส่วนประกอบที่เหมาะสมจากไฟล์ JSON โดยใช้สตริงที่ส่งผ่านเป็นพารามิเตอร์ ตรงไปตรงมามาก

ตัวอย่างโค้ด

ตอนนี้ได้เวลาตรวจสอบเนื้อและมันฝรั่งของบทความนี้แล้ว: ตัวอย่างโค้ด ฉันแค่จะเน้นย้ำสิ่งที่ฉันคิดว่าเป็นอัญมณีเล็กๆ อยู่ข้างใน แต่คุณสามารถดูซอร์สโค้ดแบบเต็มได้โดยตรงในที่เก็บทุกเวลา

การใช้ไฟล์กำหนดค่าเพื่อสร้าง UI โดยอัตโนมัติ

ฉันได้กล่าวถึงบางส่วนแล้ว แต่ฉันคิดว่ามันคุ้มค่าที่จะสำรวจรายละเอียดเบื้องหลังตัวสร้างนี้ ส่วนสำคัญที่อยู่เบื้องหลังมัน (ไฟล์ index.js ภายในโฟลเดอร์ /ui ) คือมันเป็นตัวห่อหุ้มรอบวัตถุ Blessed และวิธีที่น่าสนใจที่สุดในนั้นก็คือวิธี loadScreen

วิธีนี้จะดึงการกำหนดค่า (ผ่านโมดูลการกำหนดค่า) สำหรับหน้าจอหนึ่งๆ และดำเนินการผ่านเนื้อหา โดยพยายามสร้างวิดเจ็ตที่เหมาะสมตามประเภทขององค์ประกอบแต่ละส่วน

 loadScreen: function(sname, extras, done) { if(typeof extras == "function") { done = extras } let screen = config.get('screens.' + sname) let screenElems = {} if(this.screenElements.length > 0) { //remove previous screen this.screenElements.map( e => e.detach()) this.screen.realloc() } Object.keys(screen.elements).forEach( eName => { let elemObj = null let element = screen.elements[eName] if(element.type == 'window') { elemObj = this.setUpWindow(element) } if(element.type == 'input') { elemObj = this.setUpInputBox(element) } if(element.type == 'input-prompt') { elemObj = this.setUpInputBox(element) } screenElems[eName] = { meta: element, obj: elemObj } }) if(typeof extras === 'object' && extras.flashmessage) { this.setUpAlert(extras.flashmessage) } this.renderScreen() let logicPath = require(screen.file) logicPath.init(screenElems, this) done() },

อย่างที่คุณเห็น โค้ดค่อนข้างยาว แต่ตรรกะเบื้องหลังนั้นง่ายมาก:

  1. มันโหลดการกำหนดค่าสำหรับหน้าจอเฉพาะปัจจุบัน
  2. ล้างวิดเจ็ตที่มีอยู่ก่อนหน้านี้
  3. ผ่านทุกวิดเจ็ตและสร้างอินสแตนซ์
  4. หากมีการส่งการแจ้งเตือนพิเศษเป็นข้อความแฟลช (ซึ่งโดยพื้นฐานแล้วเป็นแนวคิดที่ฉันขโมยมาจาก Web Dev ซึ่งคุณตั้งค่าข้อความให้แสดงบนหน้าจอจนกว่าจะมีการรีเฟรชครั้งถัดไป)
  5. แสดงหน้าจอจริง
  6. และสุดท้าย ต้องใช้ตัวจัดการหน้าจอและดำเนินการตามวิธี "เริ่มต้น"

แค่นั้นแหละ! คุณสามารถตรวจสอบวิธีการที่เหลือได้ ซึ่งส่วนใหญ่เกี่ยวข้องกับวิดเจ็ตแต่ละรายการและวิธีแสดงผล

การสื่อสารระหว่าง UI และตรรกะทางธุรกิจ

แม้ว่าในระดับใหญ่ UI แบ็กเอนด์และเซิร์ฟเวอร์แชททั้งหมดมีการสื่อสารแบบแบ่งชั้นบ้าง ส่วนหน้านั้นต้องการสถาปัตยกรรมภายในอย่างน้อยสองชั้น ซึ่งองค์ประกอบ UI แท้จริงโต้ตอบกับชุดของฟังก์ชันที่แสดงถึงตรรกะหลักภายในโปรเจ็กต์นี้โดยเฉพาะ

ไดอะแกรมต่อไปนี้แสดงสถาปัตยกรรมภายในสำหรับไคลเอ็นต์ข้อความที่เรากำลังสร้าง:

(ตัวอย่างขนาดใหญ่)

ให้ฉันอธิบายเพิ่มเติมเล็กน้อย ดังที่ได้กล่าวไว้ข้างต้น loadScreenMethod จะสร้างการนำเสนอ UI ของวิดเจ็ต (สิ่งเหล่านี้เป็นอ็อบเจกต์ที่ได้รับพร) แต่สิ่งเหล่านี้ถูกรวมไว้เป็นส่วนหนึ่งของวัตถุตรรกะหน้าจอ ซึ่งเป็นที่ที่เราตั้งค่าเหตุการณ์พื้นฐาน (เช่น onSubmit สำหรับกล่องอินพุต)

ให้ฉันยกตัวอย่างที่เป็นประโยชน์กับคุณ นี่คือหน้าจอแรกที่คุณเห็นเมื่อเริ่มต้นไคลเอ็นต์ UI:

(ตัวอย่างขนาดใหญ่)

มีสามส่วนบนหน้าจอนี้:

  1. คำขอชื่อผู้ใช้
  2. ตัวเลือกเมนู / ข้อมูล
  3. หน้าจออินพุตสำหรับตัวเลือกเมนู

โดยพื้นฐานแล้ว สิ่งที่เราต้องทำคือขอชื่อผู้ใช้แล้วขอให้พวกเขาเลือกหนึ่งในสองตัวเลือก (ไม่ว่าจะเริ่มเกมใหม่หรือเข้าร่วมเกมที่มีอยู่)

รหัสที่ดูแลมีดังต่อไปนี้:

 module.exports = { init: function(elements, UI) { this.elements = elements this.UI = UI this. this.setInput() }, moveToIDRequest: function(handler) { return this.UI.loadScreen('id-requests', (err, ) => { }) }, createNewGame: function(handler) { handler.createNewGame(this.UI.gamestate.APIKEY, (err, gameData) => { this.UI.gamestate.gameID = gameData._id handler.joinGame(this.UI.gamestate, (err) => { return this.UI.loadScreen('main-ui', { flashmessage: "You've joined game " + this.UI.gamestate.gameID + " successfully" }, (err, ) => { }) }) }) }, setInput: function() { let handler = require(this.elements["input"].meta.handlerPath) let input = this.elements["input"].obj let usernameRequest = this.elements['username-request'].obj let usernameRequestMeta = this.elements['username-request'].meta let question = usernameRequestMeta.params.content.trim() usernameRequest.setValue(question) this.UI.renderScreen() let validOptions = { 1: this.moveToIDRequest.bind(this), 2: this.createNewGame.bind(this) } usernameRequest.on('submit', (username) => { logger.info("Username:" +username) logger.info("Playername: " + username.replace(question, '')) this.UI.gamestate.playername = username.replace(question, '') input.focus() input.on('submit', (data) => { let command = input.getValue() if(!validOptions[+command]) { this.UI.setUpAlert("Invalid option: " + command) return this.UI.renderScreen() } return validOptions[+command](handler) }) }) return input } }

ฉันรู้ว่าโค้ดเยอะ แต่เน้นที่วิธีการ init สิ่งสุดท้ายที่ทำคือการเรียกเมธอด setInput ซึ่งดูแลการเพิ่มเหตุการณ์ที่ถูกต้องลงในกล่องอินพุตที่ถูกต้อง

ดังนั้น ด้วยบรรทัดเหล่านี้:

 let handler = require(this.elements["input"].meta.handlerPath) let input = this.elements["input"].obj let usernameRequest = this.elements['username-request'].obj let usernameRequestMeta = this.elements['username-request'].meta let question = usernameRequestMeta.params.content.trim()

เรากำลังเข้าถึงวัตถุที่ได้รับพรและได้รับการอ้างอิง เพื่อให้เราสามารถตั้งค่าเหตุการณ์การ submit ได้ในภายหลัง ดังนั้น หลังจากที่เราส่งชื่อผู้ใช้ เราจะเปลี่ยนโฟกัสไปที่ช่องป้อนข้อมูลที่สอง (ตามตัวอักษรด้วย input.focus() )

ขึ้นอยู่กับตัวเลือกที่เราเลือกจากเมนู เรากำลังเรียกใช้วิธีใดวิธีหนึ่งดังต่อไปนี้:

  • createNewGame : สร้างเกมใหม่โดยโต้ตอบกับตัวจัดการที่เกี่ยวข้อง
  • moveToIDRequest : แสดงหน้าจอถัดไปเพื่อขอ ID เกมเพื่อเข้าร่วม

การสื่อสารกับ Game Engine

สุดท้ายแต่ไม่ท้ายสุด (และทำตามตัวอย่างด้านบน) หากคุณกด 2 คุณจะสังเกตเห็นว่าเมธอด createNewGame ใช้เมธอดของตัวจัดการ createNewGame แล้ว joinGame เข้าร่วมเกม (เข้าร่วมเกมทันทีหลังจากสร้างเกม)

ทั้งสองวิธีนี้มีขึ้นเพื่อทำให้การโต้ตอบกับ API ของ Game Engine ง่ายขึ้น นี่คือรหัสสำหรับตัวจัดการของหน้าจอนี้:

 const request = require("request"), config = require("config"), apiClient = require("./apiClient") let API = config.get("api") module.exports = { joinGame: function(apikey, gameId, cb) { apiClient.joinGame(apikey, gameId, cb) }, createNewGame: function(apikey, cb) { request.post(API.url + API.endpoints.games + "?apikey=" + apikey, { //creating game body: { cartridgeid: config.get("app.game.cartdrigename") }, json: true }, (err, resp, body) => { cb(null, body) }) } }

คุณจะเห็นสองวิธีในการจัดการกับพฤติกรรมนี้ วิธีแรกใช้คลาส apiClient ซึ่งรวมการโต้ตอบกับ GameEngine ไว้ในอีกชั้นหนึ่งของการเป็นนามธรรม

วิธีที่สองดำเนินการโดยตรงโดยส่งคำขอ POST ไปยัง URL ที่ถูกต้องพร้อมเพย์โหลดที่ถูกต้อง ไม่มีอะไรแฟนซีทำหลังจากนั้น เราแค่ส่งเนื้อความของการตอบกลับกลับไปที่ตรรกะ UI

หมายเหตุ : หากคุณสนใจในเวอร์ชันเต็มของซอร์สโค้ดสำหรับลูกค้ารายนี้ คุณสามารถตรวจสอบได้ที่นี่

คำพูดสุดท้าย

นี่เป็นโปรแกรมสำหรับไคลเอ็นต์แบบข้อความสำหรับการผจญภัยแบบข้อความของเรา ฉันครอบคลุม:

  • วิธีจัดโครงสร้างแอปพลิเคชันไคลเอนต์
  • ฉันใช้ Blessed เป็นเทคโนโลยีหลักในการสร้างเลเยอร์การนำเสนอได้อย่างไร
  • วิธีจัดโครงสร้างการโต้ตอบกับบริการแบ็คเอนด์จากไคลเอนต์ที่ซับซ้อน
  • และหวังว่าจะมีที่เก็บเต็มรูปแบบ

และในขณะที่ UI อาจดูไม่เหมือนเวอร์ชันดั้งเดิมทุกประการ แต่ก็บรรลุวัตถุประสงค์ หวังว่าบทความนี้จะทำให้คุณมีแนวคิดเกี่ยวกับการออกแบบความพยายามดังกล่าว และคุณมีแนวโน้มที่จะลองทำด้วยตัวเองในอนาคต Blessed เป็นเครื่องมือที่ทรงพลังอย่างแน่นอน แต่คุณจะต้องอดทนกับมันในขณะที่เรียนรู้วิธีใช้งานและวิธีนำทางผ่านเอกสารของพวกเขา

ในตอนต่อไปและตอนสุดท้าย ฉันจะพูดถึงวิธีที่ฉันเพิ่มเซิร์ฟเวอร์การแชททั้งในส่วนแบ็คเอนด์และสำหรับไคลเอ็นต์ข้อความนี้

เจอกันใหม่ตอนหน้าค่ะ!

ส่วนอื่นๆ ของซีรีส์นี้

  • ตอนที่ 1: บทนำ
  • ส่วนที่ 2: การออกแบบเซิร์ฟเวอร์ Game Engine
  • ตอนที่ 4: การเพิ่มแชทในเกมของเรา