SHIROのIchigoJam日記

マイコン「IchigoJam」(イチゴジャム)の電子工作とプログラミングをメインに

UNOカードゲーム

Advent Calendar 2021「IchigoJam」にエントリーしています。

★すぐに遊べるIchigoJam web版はこちら。

冬の夜長に楽しめる、IchigoJam用のUNOカードゲームです。
何年も前から作りたかったのですが、ルールを簡略化して何とか2ファイルに収めました。
動画はこちら。

遊び方

f:id:shiro0922:20211123025625p:plain

f:id:shiro0922:20211117143208p:plain

  • プレイヤーは4人です。
    • YOU(自分)
    • COM1、COM2、COM3(コンピュータ)
  • カードは全部で60枚あります。
    • 色カード13枚(数字0~9・Draw Two・Reverse・Skip)×4色(青・緑・赤・黄)=52枚
    • Wildカード=4枚
    • Wild Draw Fourカード=4枚
  • プログラムを実行すると、各人のカードが5枚ずつ配られ、場札が出されます。最初にプレイする人はランダムで、進行も時計回りまたは反時計回りのランダムで始まります。
  • 自分の手番が来たら、左右の矢印キー(←→)で出すカードを選択して、スペースキーで決定します。出せるカードが無い時は、Enterキーを押して山札から1枚引きます。
    • WildカードまたはWild Draw Fourカードを出した時は、続いて色指定をします。「COL=」の表示でカーソルが点滅するので、左右の矢印キーで色を選んだ後、スペースキーで決定します。
  • 出せるカードは、場札と同じ色(青・緑・赤・黄)か、同じ種類(数字0~9・Draw Two・Reverse・Skip)のカードです。Wildカード・Wild Draw Fourカードは例外で、いつでも出すことができます。
  • Reverseカードが出されたら、進行が逆回りになります。
  • Skipカードが出されたら、次のプレイヤーが飛ばされて、その次のプレイヤーの番になります。
  • Wildカードが出されたら、そのプレイヤーが指定した色のカードしか、次のプレイヤーは出せません。ただしWildカード・Wild Draw Fourカードは出せます。出せない場合は、誰かが1枚出すまで色指定が次のプレイヤーへ持ち越されます。
  • Draw Twoカードが出されたら、次のプレイヤーは山札から2枚引きます。続けてDraw Twoカードが出されたら、その次のプレイヤーが4枚引きます。さらに続けてDraw Twoカードが出されたら、その次のプレイヤーが6枚…と加算されます。カードが山札から引かれた後はリセットされて、次のプレイヤーは普通に同じ色、または同じDraw Twoカードを出せます。
  • Wild Draw Fourカードが出されたら、次のプレイヤーは山札から4枚引きます。続けてWild Draw Fourカードが出されたら、その次のプレイヤーが8枚引きます。さらに続けてWild Draw Fourカードが出されたら、その次のプレイヤーが12枚…と加算されます。カードが山札から引かれた後、その次のプレイヤーは指定された色のカードを出さなければいけません。カードが山札から引かれた後は、次のプレイヤーは続けてWild Draw Fourカードを出せますが、引く枚数はリセットされます。
    • Draw TwoカードにWild Draw Fourカードを重ねる、あるいはWild Draw FourカードにDraw Twoカードを重ねることはできません。
  • 場に出せるカードが無い、あるいはDraw TwoカードやWild Draw Fourカードによって山札からカードを引いた時、もし引いた中に場に出せるカードがあってもすぐに出すことはできません。次の手番まで待ってください。
  • 手札が残り1枚になると、自動的に「UNO!」がコールされます。
  • 手札を全て場に出すと上がりです。プレイヤーの誰かが上がるとそのゲームが終了します。上がれなかったプレイヤーは、手持ちのカードから合計点が計算されます。数字カードはその数字、Draw Two・Reverse・Skipカードは20点、Wild・Wild Draw Fourカードは50点です。これはマイナス点なので、少ない方が良い成績です。
  • 自動で次のゲームが始まり、点数表示が更新されます。
  • 何回かゲームを行い、プレイヤーの誰かが500点に達したら終了です。500点になってもプログラムは止まらないので、ESCキーを押して止めてください。500点勝負には10~15ゲームくらいかかるので、「今回は300点まで」などと自分で制限点を決めて遊んでもいいでしょう。

プログラム

プログラム0をファイル0、プログラム1をファイル1にSAVEして、「LRUN 0」でプログラム0から起動してください。連続したファイル番号であれば、0番・1番でなくても構いません。
※12/1修正:乱数初期化を追加。特にweb版では初回にいつも同じカードが配られてしまうのですが、その後の展開はプレイするたびに変わってきます。

プログラム0(初期設定、画面表示、点数計算)

1 CLV:LET[90],0,11,0,5,10,2,19,5:COPY228*8,48*8,80:LET[0],68,82,83,73,70,87,66,71,82,89:FORI=0TO9:COPY(238+I)*8,[I]*8,8:NEXT
2 X=0:FORS=1TO4:FORN=0TO12:[X]=S*16+N:X=X+1:NEXT:[X]=13:[X+1]=14:X=X+2:NEXT:FORQ=0TO3:FORN=1TO5
3 B=RND(60):IF[B]/256CONT
4 [B]=[B]+(Q+1)*256:NEXT:NEXT:LET[98],5,5,5,5
5 E=RND(60):IF[E]/256OR[E]&15>9CONT
6 C=[E]:N=C&15:S=(C/16)&15:[E]=[E]+#500:G=G+1:Z=0:P=RND(4):T=RND(2)*2-1:W=0
7 SRNDTICK():CLS:?"GAME=";G:LC12,7:?CHR$(226+(T<0));"[";:B=C:GSB16:?"]";CHR$(227-(T<0)):H=D+F:IFHLC13,8:?"DR=";H
8 IFWLC12,9:?"COL=";CHR$(243+W)
9 FORQ=0TO3:H=90+Q*2:LC[H],[H+1]:?CHR$(Q=P);:IFQ?"COM";Q;ELSE?"YOU";
10 A=[98+Q]:?":";A;"/";[85+Q]:IFA=1LC[H]+1,[H+1]+1:?"*UNO!*"
11 NEXT:LC[90],[91]+2:J=0:FORX=0TO59:IF[X]/256=1B=[X]:GSB16:?" ";:[60+J]=X:J=J+1:IFJ%10=0?
12 NEXT:IFUU=0:PLAY"T240EC"
13 IF!ZWAIT70:LC0,-1:LRUNFILE()+1
14 LC8:?"*END*":BEEP10,30:FORX=0TO59:B=[X]:P=B/256:A=B&15:IFA>12A=50ELSEIFA>9A=20
15 [84+P]=[84+P]+A:NEXT:WAIT99:GOTO2
16 M=B&15:R=(B/16)&15:?CHR$(243+R);CHR$(228+M);:RTN

プログラム1(プレイ処理)

1 K=-1:IFPGSB20 ELSEGSB12
2 IFK=-1GSB33:GOTO9
3 BEEP:[E]=[E]&255:E=K:[E]=#500+[E]&255:C=[E]:N=C&15:S=(C/16)&15:[98+P]=[98+P]-1:IF![98+P]Z=1:GOTO10
4 U=([98+P]=1):IFN<13W=0
5 IFN=10D=D+2
6 IFN=11T=-T
7 IFN=12GSB11
8 IFN=14F=F+4
9 GSB11
10 LC0,-1:LRUNFILE()-1,7
11 P=P+T:P=P&3:RTN
12 CLK:Y=0
13 LC[90]+(Y%10)*3,[91]+2+Y/10,1:WAIT6:J=INKEY():IFJ=10RTN
14 Y=Y-(J=28)*(Y>0)+(J=29)*(Y<[98]-1):IFJ!=32GOTO13
15 X=[60+Y]:B=[X]:GSB27:IFK=-1GOTO13
16 IFRRTN
17 W=1
18 LC12,9,1:?"COL=";CHR$(243+W);:WAIT6:J=INKEY():W=W-(J=28)*(W>1)+(J=29)*(W<4):IFJ!=32CONT
19 V=W:RTN
20 X=60
21 X=X-1:IFX<0RTN
22 B=[X]:IFB/256!=P+1GOTO21
23 GSB27:IFK=-1GOTO21
24 IFRRTN
25 W=RND(4)+1:IFW=VCONT
26 V=W:RTN
27 K=-1:M=B&15:R=(B/16)&15:IFD>0IFM=10K=X:RTNELSERTN
28 IFF>0IFM=14K=X:RTNELSERTN
29 IFWIFR=WOR!RK=X:RTNELSERTN
30 IF!RK=X
31 IFR=SORM=NK=X
32 RTN
33 A=1:IFDA=D
34 IFFA=F
35 FORI=1TOA
36 X=RND(60):B=[X]:IFB/256CONT
37 [X]=[X]|256*(P+1):[98+P]=[98+P]+1:BEEP30,2:WAIT9:NEXT:D=0:F=0:RTN

IchigoJam web版

→IchigoJam web版はこちら。
IchigoJam webは遅いので、プログラム0のWAITを調整しています。

プログラム解説:カードの管理

f:id:shiro0922:20211123032052p:plain

  • プログラム0で、60枚のカードを順番に配列変数[0]~[59]に記憶させています。各配列の下位8ビットがカードの中身を表しています。ビット0~3がカードの種類、ビット4~7がカードの色を表します。
  • ゲームプレイ中は配列の状態ビット(ビット8~10)を変化させて管理します。例えばYOU(自分)が山札からカードを引く時は、山札の中からランダムにカードを選んで、カードの状態ビットを0(山札)→1(YOU所有)に変えます。YOUのカードを表示する時は、60個の配列を[0]~[59]までスキャンして、状態ビット=1(YOU所有)のカードを抽出して表示します。こうすることで配列60個だけでカードを管理できると同時に、カードの値の入れ替えなどの作業を無くしてプログラムを簡略化しています。
  • カード管理とは別に、YOUプレイヤーのカード表示とキー入力用に配列変数を24個使っています([60]~[83])。[84]以降は各プレイヤーの点数記憶に使っているため、YOUプレイヤーが25枚以上のカードをためてしまうと動作がおかしくなります。(そこまで増えることはまず無いでしょうが)
  • カードを簡単に表示するために、プログラム0の1行目で文字フォントデータをPCG領域へCOPYして、文字コード228からカードの種類文字「0123456789DRSIF」、文字コード243から色の文字「WBGRY」を配置しています。

攻略作戦

  • UNOの基本的な作戦は、このプログラムでも有効です。
    • 自分のカードを減らすために、WildやWild Draw fourを出して、自分が多く持っている色を指定する。
    • 隣のプレイヤーが上がりそうだったら、Draw TwoやWild Draw Fourを出してカードを取らせる。あるいはSkipで飛ばして回避、Reverseで逆回りにして回避する。
    • 他のプレイヤーが上がりそうになったら、自分のマイナス点を減らすために役札を出して無くす。
  • このプログラム独自の傾向としては、
    • 通常のUNOはカードが108枚ありますが、このプログラムは色カードを減らして計60枚にしています。Wild Draw Fourが出る確率がかなり高いので、4枚・8枚・12枚取らされる「事故」が多発してなかなか上がれません。その一方で、他のプレイヤーの上がりもかなり的確に阻止してくれます。ゲームがかなりダイナミックに動きますし、終了時のマイナス点が大きいので早く500点に到達します。
    • 山からカードを引いた時、出せるカードがあってもすぐに出せないのが地味に痛いです。(すぐ出せるプログラムにもできるのですが、プレイのスピード感とわかりやすさを優先しています)
    • COMプレイヤーは、出せるカードを出しているだけでほとんど何も考えていません(Wildの色指定だけ多少考えています)。何も考えていないので、逆に言うと手が読めません。
  • 以上の傾向から攻略作戦は難しいのですが、当たり前ですが「自分が上がる」のを目指すのと同時に「他のプレイヤーを先に上がらせない」こと、そして自分のマイナス点を減らすことが重要です。役札をうまく駆使すれば、500点勝負ならトップになれるでしょう。
  • 「役札上がりあり」のルールなので、例えばWildやWild Draw Fourを最後の1枚に持っていると、手番が回ってくれば(Draw Twoなどで強制ドローでない限り)必ず上がることができます。ただし他のプレイヤーに先に上がられてしまうと、自分がマイナス50点を背負うリスクがあります。

★12/1追加:56枚バージョン

上の60枚バージョンだとWild Draw Fourが出過ぎて疲れる…という人のために、WildとWild Draw Fourを2枚ずつに減らした56枚バージョンを作ってみました。
野球に例えれば、60枚版が派手な打撃戦なら、56枚版は緊迫した投手戦。淡々とゲームが進みますが、互いの手の内を読み合って失点を防ぎ、1つ(1枚)のミスが勝敗を分けます。ロースコアの展開になるので、500点勝負だと20~25ゲームくらいかかりますが、通常のUNOはどちらかと言えばこんな感じでしょう。

プログラム0(初期設定、画面表示、点数計算)

1 CLV:LET[90],0,11,0,5,10,2,19,5:COPY228*8,48*8,80:LET[0],68,82,83,73,70,87,66,71,82,89:FORI=0TO9:COPY(238+I)*8,[I]*8,8:NEXT
2 X=0:FORS=1TO4:FORN=0TO12:[X]=S*16+N:X=X+1:NEXT:[X]=13+S%2:X=X+1:NEXT:FORQ=0TO3:FORN=1TO5
3 B=RND(56):IF[B]/256CONT
4 [B]=[B]+(Q+1)*256:NEXT:NEXT:LET[98],5,5,5,5
5 E=RND(56):IF[E]/256OR[E]&15>9CONT
6 C=[E]:N=C&15:S=(C/16)&15:[E]=[E]+#500:G=G+1:Z=0:P=RND(4):T=RND(2)*2-1:W=0
7 SRNDTICK():CLS:?"GAME=";G:LC12,7:?CHR$(226+(T<0));"[";:B=C:GSB16:?"]";CHR$(227-(T<0)):H=D+F:IFHLC13,8:?"DR=";H
8 IFWLC12,9:?"COL=";CHR$(243+W)
9 FORQ=0TO3:H=90+Q*2:LC[H],[H+1]:?CHR$(Q=P);:IFQ?"COM";Q;ELSE?"YOU";
10 A=[98+Q]:?":";A;"/";[85+Q]:IFA=1LC[H]+1,[H+1]+1:?"*UNO!*"
11 NEXT:LC[90],[91]+2:J=0:FORX=0TO59:IF[X]/256=1B=[X]:GSB16:?" ";:[56+J]=X:J=J+1:IFJ%10=0?
12 NEXT:IFUU=0:PLAY"T240EC"
13 IF!ZWAIT70:LC0,-1:LRUNFILE()+1
14 LC8:?"*END*":BEEP10,30:FORX=0TO55:B=[X]:P=B/256:A=B&15:IFA>12A=50ELSEIFA>9A=20
15 [84+P]=[84+P]+A:NEXT:WAIT99:GOTO2
16 M=B&15:R=(B/16)&15:?CHR$(243+R);CHR$(228+M);:RTN

プログラム1(プレイ処理)

1 K=-1:IFPGSB20 ELSEGSB12
2 IFK=-1GSB33:GOTO9
3 BEEP:[E]=[E]&255:E=K:[E]=#500+[E]&255:C=[E]:N=C&15:S=(C/16)&15:[98+P]=[98+P]-1:IF![98+P]Z=1:GOTO10
4 U=([98+P]=1):IFN<13W=0
5 IFN=10D=D+2
6 IFN=11T=-T
7 IFN=12GSB11
8 IFN=14F=F+4
9 GSB11
10 LC0,-1:LRUNFILE()-1,7
11 P=P+T:P=P&3:RTN
12 CLK:Y=0
13 LC[90]+(Y%10)*3,[91]+2+Y/10,1:WAIT6:J=INKEY():IFJ=10RTN
14 Y=Y-(J=28)*(Y>0)+(J=29)*(Y<[98]-1):IFJ!=32GOTO13
15 X=[56+Y]:B=[X]:GSB27:IFK=-1GOTO13
16 IFRRTN
17 W=1
18 LC12,9,1:?"COL=";CHR$(243+W);:WAIT6:J=INKEY():W=W-(J=28)*(W>1)+(J=29)*(W<4):IFJ!=32CONT
19 V=W:RTN
20 X=56
21 X=X-1:IFX<0RTN
22 B=[X]:IFB/256!=P+1GOTO21
23 GSB27:IFK=-1GOTO21
24 IFRRTN
25 W=RND(4)+1:IFW=VCONT
26 V=W:RTN
27 K=-1:M=B&15:R=(B/16)&15:IFD>0IFM=10K=X:RTNELSERTN
28 IFF>0IFM=14K=X:RTNELSERTN
29 IFWIFR=WOR!RK=X:RTNELSERTN
30 IF!RK=X
31 IFR=SORM=NK=X
32 RTN
33 A=1:IFDA=D
34 IFFA=F
35 FORI=1TOA
36 X=RND(56):B=[X]:IFB/256CONT
37 [X]=[X]|256*(P+1):[98+P]=[98+P]+1:BEEP30,2:WAIT9:NEXT:D=0:F=0:RTN