ASP.NETお試し会
本記事はMuroran Institute of Technology Advent Calendar 2019 12/7の記事です.
はじめに
唐突にASP.NETお試し会を開催しました. 参加者は3名(表示名:参加者1,参加者2,参加者3)です. 内容はASP.NETを用いたジャンケンAPIの試作です. 以下では,開催に至った経緯,ASP.NETお試し会でやったこと,試作したジャンケンAPIの動作例,3名の感想を書きます.
開催に至った経緯
参加者1は普段からUnityでゲーム開発をしています. さらに,参加者1はオンラインゲームの開発に挑戦しようとしています. 参加者1は,特にサーバサイドにおける通信を扱うプログラミングについて,参加者2から教えてもらうことにしました. 参加者2は,参加者1がC#に慣れていると考え,ASP.NETを利用してサーバプログラムを開発したら良いと考えました. しかし,参加者2はサーバプログラムの開発経験はKtorを利用した簡単なWebAPIの開発だけであり,ASP.NETについては全く知らないため教えることができません. 参加者2はKtorを用いないサーバプログラム開発にも興味を持っていたため,参加者1と一緒に調べながら簡単なサーバプログラムを試作し,理解を深めることにしました. そこに参加者3が合流して,ASP.NETお試し会が開催されることとなりました.
ASP.NETお試し会でやったこと
ASP.NETお試し会では以下の作業を行いました.
- VisualStudioの起動とプロジェクトの作成
- ジャンケンAPIの試作
- モデルの構成
- コントローラーの構成
VisualStudioの起動とプロジェクトの作成
VisualStudioの起動して,「Welcome Page」から「New Project...」,「ASP.NET Core Web API」,「Next」と進み,「Project Name」と「Solution Name」を埋め,「Create」を選択してプロジェクトを作成しました.
実行し,ブラウザからhttp://localhost: 24635/api/values
にアクセスすると["value1","value2"]
と表示されました.
ポート番号は人にプロジェクトによって異なるようです.
初期状態ではこのURLにアクセスするとControllers/ValuesController.cs
のpublic IEnumerable<string> Get()
メソッドが呼ばれているようです.
Controllers/ValuesController.cs
を以下に示します.
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; namespace Test3.Controllers { [Route("api/[controller]")] public class ValuesController : Controller { // GET api/values [HttpGet] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } // GET api/values/5 [HttpGet("{id}")] public string Get(int id) { return "value"; } // POST api/values [HttpPost] public void Post([FromBody]string value) { } // PUT api/values/5 [HttpPut("{id}")] public void Put(int id, [FromBody]string value) { } // DELETE api/values/5 [HttpDelete("{id}")] public void Delete(int id) { } } }
ジャンケンAPIの試作
ASP.NETのプロジェクトの初期動作を確認した後,以下のモデルとコントローラを実装し,ジャンケンAPIを試作しました.
モデルの構成
モデルはゲームのリストとゲームを操作するコマンドで構成されます. ゲームはプレイヤ2人の名前と出した手(グー,チョキまたはパー)を保持します. ゲームを操作するコマンドは以下の通りです.
- make:
- 入力:プレーヤー0の名前
- 出力:ルームID
- 新たなゲームを作成する
- enter :
- 入力:入室する部屋のルームID,プレーヤー1の名前
- 出力:なし
- 既存の部屋に入室する
- tryPlay :
- 入力:ルームID
- 出力:プレイを開始できるかどうか
- プレイできるか確認する
- play :
- 入力:ルームID,プレーヤー名,グー,チョキまたはパー
- 出力:なし
- グー,チョキまたはパーを出す
- tryResult:
- 入力:ルームID
- 出力:勝敗を判定できるかどうか
- 勝敗を判定できるか確認する
- result :
- 入力:ルームID
- 出力:勝者と敗者
- 勝敗を確認する
以上のモデルを実装したModelクラスを以下に示します.
using System; using System.Collections.Generic; namespace Aspdotnet.Models { public enum Move { Rock, Paper, Scissors, None } public class Result { String Winner; String Loser; public Result(String winner, String loser) { Winner = winner; Loser = loser; } public bool IsTie() { return Winner == null && Loser == null; } public override string ToString() { return "Winner: " + Winner + ", " + "Loser: " + Loser; } } class Game { public String player0; public Move move0 = Move.None; public String player1; public Move move1 = Move.None; public bool IsReady() { return player0 != null && player1 != null && move0 == Move.None && move1 == Move.None; } public bool HasFinished() { return player0 != null && player1 != null && move0 != Move.None && move1 != Move.None; } public Result GetResult() { if (!HasFinished()) { return null; } if (move1 == Move.Paper && move0 == Move.Scissors || move1 == Move.Scissors && move0 == Move.Rock || move1 == Move.Rock && move0 == Move.Paper) { return new Result(player0, player1); } if (move0 == Move.Paper && move1 == Move.Scissors || move0 == Move.Scissors && move1 == Move.Rock || move0 == Move.Rock && move1 == Move.Paper) { return new Result(player1, player0); } return new Result(null, null); } } public class Model { List<Game> games = new List<Game>(); static Model instance = new Model(); public static Model GetInstance() { return instance; } Model() { } public override string ToString() { string s = ""; for (int i = 0; i < games.Count; ++i) { var g = games[i]; s += i.ToString() + " : "; s += "player0 = " + g.player0 + "; "; s += "player1 = " + g.player1 + "; "; s += "move0 = " + g.move0 + "; "; s += "move1 = " + g.move1 + "; "; } return s; } public int Make(String player0) { if (player0 == null) { return -1; } int roomId = games.Count; var game = new Game(); game.player0 = player0; games.Add(game); return roomId; } public void Enter(int roomId, String player1) { if (player1 == null) { return; } if (roomId < 0 || roomId >= games.Count) { return; } var game = games[roomId]; game.player1 = player1; } public bool TryPlay(int roomId) { if (roomId < 0 || roomId >= games.Count) { return false; } var game = games[roomId]; return game.IsReady(); } public void Play(int roomId, String player, Move move) { if (roomId < 0 || roomId >= games.Count || move == Move.None) { return; } var game = games[roomId]; if (game.player0 == player && game.move0 == Move.None) { game.move0 = move; } if (game.player1 == player && game.move1 == Move.None) { game.move1 = move; } } public bool TryResult(int roomId) { if (roomId < 0 || roomId >= games.Count) { return false; } return games[roomId].HasFinished(); } public Result Result(int roomId) { if (roomId < 0 || roomId >= games.Count) { return null; } var game = games[roomId]; if (!game.HasFinished()) { return null; } return games[roomId].GetResult(); } } }
コントローラーの構成
クライアントがHTTPのGETメソッドを用いてジャンケンAPIにアクセスして,上のコマンドを実行できるようにします. コマンドへの入力はURLに含めることにしました. これを実装したJankenControllerクラスを以下に示します.
using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; namespace Aspdotnet.Controllers { [Route("api/[controller]")] public class JankenController : Controller { // GET api/janken/make/player0Name [HttpGet("make/{player0}")] public int GetMake(string player0) { var model = Models.Model.GetInstance(); int roomId = model.Make(player0); Console.WriteLine(model); return roomId; } // GET api/janken/enter/roomId/player1Name [HttpGet("enter/{roomId}/{player1}")] public string GetEnter(int roomId, string player1) { var model = Models.Model.GetInstance(); model.Enter(roomId, player1); Console.WriteLine(model); return ""; } // GET api/janken/tryPlay/roomId/ [HttpGet("tryPlay/{roomId}")] public bool GetTryPlay(int roomId) { var model = Models.Model.GetInstance(); var isReady = model.TryPlay(roomId); Console.WriteLine(model); return isReady; } // GET api/janken/play/roomId/player0Name/Rock [HttpGet("play/{roomId}/{player}/{move}")] public string GetPlay(int roomId, string player, string move) { var model = Models.Model.GetInstance(); Models.Move m = Models.Move.None; switch (move) { case "Rock": m = Models.Move.Rock; break; case "Paper": m = Models.Move.Paper; break; case "Scissors": m = Models.Move.Scissors; break; default: return ""; } model.Play(roomId, player, m); Console.WriteLine(model); return ""; } // GET api/janken/tryResult/roomId/ [HttpGet("tryResult/{roomId}")] public bool GetTryResult(int roomId) { var model = Models.Model.GetInstance(); bool hasFinished = model.TryResult(roomId); Console.WriteLine(model); return hasFinished; } // GET api/janken/result/roomId/ [HttpGet("result/{roomId}")] public string GetResult(int roomId) { var model = Models.Model.GetInstance(); Models.Result result = model.Result(roomId); Console.WriteLine(model); return result.ToString(); } } }
試作したジャンケンAPIの動作例
最後に試作したジャンケンAPIの動作確認をしました. 動作例を以下に示します.
curl http://localhost:24635/api/janken/make/Sankasha1 # => 1 curl http://localhost:24635/api/janken/enter/0/Sankasha2 curl http://localhost:24635/api/janken/tryPlay/0 # => true curl http://localhost:24635/api/janken/play/0/Sankasha1/Paper curl http://localhost:24635/api/janken/play/0/Sankasha2/Rock curl http://localhost:24635/api/janken/tryResult/0 # => true curl http://localhost:24635/api/janken/result/0 # => Winner: Sankasha1, Loser: Sankasha2
3名の感想
参加者1
ASP.NETを通じて、HTTP通信を組み込んだソフトウェアをどう組み立てるか、どう実装した方が良いかを考える経験が出来ました
参加者2
見様見真似でとりあえず,動作するジャンケンAPIを作成することができた. その点では,やはりVisualStudioとASP.NETはすごいと思った. ただ,実際にVPSなどで動作させる場合にどうすれば良いか想像できなかった. 当たり前だが,今後ASP.NETを利用する場合には使い方をきちんと調べる必要がある.
ジャンケンAPIは,最初は単純なように思えたが,実際に試作してみると状態を管理するのは意外と面倒だった. また,入力が全てURLに含まれているため,不正なプレイが可能かもしれない.
ASP.NETお試し会を開催した結果,アドベントカレンダーを書くこともできため良かった.
参加者3
もう少し難しいと思ってたのですが,実装自体は予想以上に楽だったので驚きました.使い方次第でいろいろ面白いことが出来そうなので,自作ゲームで生かしてみたいです.